シューティングゲームの当たり判定について考える

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

シューティングゲームの当たり判定について考える

投稿記事 by せんちゃ » 12年前

できる限りならば当たり判定とオブジェクトは別と考えたいので、
座標も当たり判定オブジェクトとエネミーは別々にもたせます。

CODE:

//"Collision.h"
namespace Collision{
enum {
	OBJECT_TYPE_ENEMY		, 
	OBJECT_TYPE_PLAYER		,
	OBJECT_TYPE_BULLET		,
	OBJECT_TYPE_ITEM		,

	COLLISION_NORMAL		,
	COLLISION_HIT_ATTACK	, 
	COLLISION_HIT_ITEM		,


	USER_PLAYER				,
	USER_ENEMY				,
};




//	円形コリジョンオブジェクト
//	物体オブジェクトの衝突判定はすべてこれを経由する
class Circle{
private :
	Vector	pos;
	float	radius;


	struct{
		unsigned int		objectId		:	8;	//	オブジェクトID
		unsigned int		objectType		:	8;	//	オブジェクト種類(エネミー、プレイヤー、弾幕、アイテムなど)
		unsigned int		user			:	8;	//	衝突判定オブジェクトの所持者
		unsigned int		collisionType	:	8;	//	衝突判定タイプ。通常の衝突、アイテムオブジェクト衝突、ダメージオブジェクト衝突など

		unsigned int		handleId		:	30;
		unsigned int		isHit			:	1;
		unsigned int		isActive		:	1;
	};
public :
	Circle();
	~Circle();
	bool IsHit()			{ return isHit;			}
	bool IsActive()			{ return isActive;		}
	int  getObjectId()		{ return objectId;		}
	int  getObjectType()	{ return objectType;	}
	int  getUser()			{ return user;			}
	int  getCollisionType()	{ return collisionType;	}

	void disableCollision	();
	void enableCollision	();
	void setup				( int objectId , int objectType , int user , int collisionType );
	void setPoint			( float x , float y , float radius );
	void setPoint			( float x , float y );
	bool checkCollision		( float x , float y , float radius );
	bool checkCollision		( Circle* c );
	void onHit();
	void relHit();
	virtual void draw();

};


//	攻撃判定コリジョン
//	キャラクターオブジェクトへのダメージを与える
class HitAttack : public Circle{
private	:
	int damage;
public	:
	HitAttack()
	{
		damage = 0;
	}

	void	setDamage( int damage )	{	this->damage	=	damage;	}
	int		getDamage()				{	return damage; }
};

//	アイテム判定コリジョン
//	アイテムによるパワーアップ、エクステンド、ボム増加などをコントロールする
class HitItem : public Circle{
private		:
	//	4バイト
	struct{
		unsigned int pow	:	15;
		unsigned int bonus	:	15;
		unsigned int extend :	1;
		unsigned int bomb	:	1;
	};
public		:
	void setPowerUp	( int pow		){	this->pow		=	pow;	}
	void setExtend	( int extend	){	this->extend	=	extend; }
	void setBomb	( int bomb		){	this->bomb		=	bomb;	}
	void setBonus	( int bonus		){	this->bonus		=	bonus;	}
};



void initialize();
void update();

}

といった感じでコリジョン処理。
今後アイテムやエネミーなど種類が増えても
このオブジェクトをそれぞれのフィールドで生成するだけで当たり判定が取れるようになります。


判定タイプはいろいろあって、STGの場合は
・判定したらダメージを与える
・判定したらアイテム処理をする

などがあるのであらかじめそういったクラスは作っておきます。
ダメージ判定をプレイヤーやエネミーに指定したい場合はHitAttackクラスを持てば
設定したパラメータだけのダメージを指定のオブジェクトに与えられるし
アイテム判定を持たせれば指定した数値分のボーナスポイントやボム追加やエクステンドが行えます。


CODE:

// Collision.cpp




#define		MAX_HANDLER		15000
#define		COLL_MAN		CollisionManager::getInstance()



namespace Collision{


//	■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□
//	衝突判定マネージャー
class CollisionManager{
private :
	Circle*		Handler[MAX_HANDLER];

	CollisionManager()
	{
		initialize();
		printf( "initialize \n" );
	}

	//	空いているハンドルを取得する。
	//	どこも空いていない場合はダミーとして設定されている0番を渡す。
	int getHandleId( Circle* c )
	{
		for( int i = 1 ; i IsActive() ){
				continue;
			}

			c->relHit();
			for( int j = 1 ; j IsActive() ){
					continue;
				}

				if( c != c2 ){
					if( c->checkCollision( c2 ) ){
						onHitEvent( c , c2 );
					}
				}
			}
		}
	}



	//	衝突判定イベント処理。
	//	オブジェクトの所持者情報によって処理が変化する
	void onHitEvent( Circle* c1 , Circle* c2 )
	{
		c1->onHit();
		if( c1->getObjectType() == OBJECT_TYPE_ENEMY )
		{
			if( c2->getObjectType() == OBJECT_TYPE_BULLET && c2->getUser() == USER_PLAYER )
			{
				if( c2->getCollisionType() == COLLISION_HIT_ATTACK )
				{
					HitAttack* attack = ( HitAttack* )c2;
					Enemies::collisionAttack( c1->getObjectId() , attack );		//	指定のエネミーIDにダメージ情報ハンドリング
				}
			}
		}
		else if( c1->getObjectType() == OBJECT_TYPE_PLAYER )
		{
			if( c2->getObjectType() == OBJECT_TYPE_BULLET && c2->getUser() == USER_ENEMY )
			{
				if( c2->getCollisionType() == COLLISION_HIT_ATTACK )
				{
					HitAttack* attack = ( HitAttack* )c2;
				//	Player::collisionAttack( c1->getObjectId() , attack );
				}
			}
			else if( c2->getObjectType() == OBJECT_TYPE_BULLET )
			{
				if( c2->getCollisionType() == COLLISION_HIT_ITEM )
				{
					HitItem* item = ( HitItem* )c2;
				//	Player::collisionItem( c1->getObjectId() , item );
				}
			}
		}
		else if( c1->getObjectType() == OBJECT_TYPE_BULLET )
		{
		}
		else if( c1->getObjectType() == OBJECT_TYPE_ITEM )
		{
		}
	}
};



//	■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□
//	Circle
Circle::Circle()
{
	pos.x			=	0;
	pos.y			=	0;
	radius			=	0;
	handleId		=	0;
	isHit			=	false;
	isActive		=	true;
	handleId		=	COLL_MAN.setupHandler( this );


	objectId		=	0;
	objectType		=	0;
	user			=	0;
	collisionType	=	0;
	printf( "setup id = %d \n" , handleId );
}


Circle::~Circle()
{
	COLL_MAN.freeHandler( handleId );
	printf( "free id = %d \n" , handleId );
}


void Circle::disableCollision()
{
	isActive = false;
}

void Circle::enableCollision()
{
	isActive = true;
}

void Circle::setup( int objectId , int objectType , int user , int collisionType )
{
	this->objectId		= objectId;
	this->objectType	= objectType;
	this->user			= user;
	this->collisionType = collisionType;
}

void Circle::setPoint( float x , float y , float radius )
{
	this->pos.x		= x;
	this->pos.y		= y;
	this->radius	= radius;
}

void Circle::setPoint( float x , float y )
{
	this->pos.x		= x;
	this->pos.y		= y;
}

bool Circle::checkCollision( float x , float y , float radius )
{
	if( !isActive ){
		return false;
	}

	float xx = this->pos.x - x;
	float yy = this->pos.y - y;
	float rr = this->radius + radius;
	if( ( xx * xx ) + ( yy * yy ) pos.x , c->pos.y , c->radius );
}

void Circle::onHit()
{
	isHit = true;
}

void Circle::relHit()
{
	isHit = false;
}

void Circle::draw()
{
	DrawCircle( (int)pos.x , (int)pos.y , (int)radius , 0xAAAAAA , FALSE );
}








//	公開グローバル関数
void initialize()
{
	COLL_MAN.getInstance();
}

void update()
{
	COLL_MAN.update();
}


}

マネージャークラス内で処理を行い、
指定した番号のオブジェクトに対してダメージイベントを呼び出す、といった感じです。


エネミーの実装はこんな感じ

CODE:

//    EnemyObject.h


//	挙動のベースクラス。
//	移動パターンはここを派生して記述する
class EnemyBase : public Test::EnemyObject{
private		:
	int					hp;			//	体力
	Visual				visual;		//	ビジュアル
	Vector				pos;		//	座標
	Collision::Circle	coll;		//	衝突判定
	int					objectId;	//	オブジェクト番号
public		:
	EnemyBase( float x , float y , int ENEMY_ID );
	~EnemyBase();
	int getObjectId();
	virtual void damage( int damage );
	virtual void update();
	virtual void draw();
};




void collisionAttack( int objectId , Collision::HitAttack* attack );
void initialize();

CODE:

//  EnemyObject.cpp
namespace Enemies{
//	■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□■□
//	内部マネージャー
class EnemyManager{
private	:
	EnemyBase* Handler[MAX_ENEMY+1];	//	エネミーへのイベントハンドラー
	EnemyManager()
	{
		initialize();
	}
public	:
	static EnemyManager getInstance()
	{
		static EnemyManager instance;
		return instance;
	}

	void initialize()
	{
		memset( Handler , NULL , sizeof( Handler ) );
	}

	EnemyBase* findEnemyById( int objectId )
	{
		for( int i = 1 ; i getObjectId() == objectId ){
				return Handler[i];
			}
		}

		return Handler[0];
	}

	int entryEnemyHandler( EnemyBase* enemy )
	{
		for( int i = 1 ; i damage( attack->getDamage() );
	}
}

void initialize()
{
	ENEMY_MAN.initialize();
}


}
STGでこんな作り方はやや大げさな作り方かもしれませんが…


実験的にやってみているということでここはひとつ

ISLe
記事: 2650
登録日時: 15年前

Re: シューティングゲームの当たり判定について考える

投稿記事 by ISLe » 12年前

当たり判定の形状と属性のセットをリストで持って、当たり判定オブジェクトをリスナー形式で登録するようにして、どの形状に対して当たったかをコールバックで返す仕組みにすると、もっとシンプルに実装できるのではないでしょうか。
当たり判定に関する処理をクラス内で完結させることができるので公開範囲をもっと抑えられます。

アバター
へにっくす
記事: 634
登録日時: 13年前

Re: シューティングゲームの当たり判定について考える

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

せんちゃ さんが書きました:

CODE:

    struct{
        unsigned int        objectId        :   8;  //  オブジェクトID
        unsigned int        objectType      :   8;  //  オブジェクト種類(エネミー、プレイヤー、弾幕、アイテムなど)
        unsigned int        user            :   8;  //  衝突判定オブジェクトの所持者
        unsigned int        collisionType   :   8;  //  衝突判定タイプ。通常の衝突、アイテムオブジェクト衝突、ダメージオブジェクト衝突など
 
        unsigned int        handleId        :   30;
        unsigned int        isHit           :   1;
        unsigned int        isActive        :   1;
    };
個人的には最初の4つはビットフィールドの意味があるのか?とか思ったりするw
unsigned charでええやん…
structにタグ名がないのがよけいね。
あ、アラインメント指定するのが面倒という理由なら納得ですけど。
オフトピック
#pragma pack
なんてのはないのかな。

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: シューティングゲームの当たり判定について考える

投稿記事 by せんちゃ » 12年前

へにっくすさん
最初の4つは今は8ビットでとっていますが、実際のところ8ビットもいらないデータなので
今後パラメータを追加するときにビット数を減らす可能性があるのでそういった理由からビットフィールドで割り当てています。
無名構造体なのはクラスメンバとして扱っていることと、クラス内でしかどうせ使われないし、という理由からそのようにしています。

今回書いたコードは弾幕系STGのコードになるのですが、
弾幕やエフェクトやアイテムの個数を考えるとかなりメモリを食っているので
こういう小さい部分から節約しておこうという苦肉の策ですね(´ε`;)