Маленький отважный арканоид (часть 4)

от автора

После небольшого перерыва, продолжим нашу разработку. Сегодня мы добавим в проект небольшой звуковой эффект, проигрываемый при соударении шарика с чем либо на игровом поле. О работе с SoundEngine (которой мы сегодня воспользуемся) я уже писал ранее. По этой причине, сегодня я расскажу не столько о ней, сколько о том, как ее использование отразится на разрабатываемом нами проекте.

Главным (и несколько неожиданным для меня) последствием включения в проект SoundEngine явилась необходимость перехода с IwGl на IwGx. Дело в том, что SoundEngine использует для загрузки описаний звуковых эффектов возможности подсистемы IwResManager. Само описание выглядит следующим образом:

sounds.group

CIwResGroup { 	name "sounds"  	"./sounds/contact.wav"  	CIwSoundSpec 	{ 		name		"contact" 		data		"contact" 		vol 		0.9 		loop		false 	}  	CIwSoundGroup 	{ 		name		"sound_effects" 		maxPolyphony 	8 		killOldest	true 		addSpec		"contact" 	}  } 

Попытка включения в проект IwResManager приводит к тому, что он просто перестает собираться:

image

Добавление в проект IwGeom также мало помогает:

image

После бесплодных попыток исправить ситуацию, я решил отказаться от использования IwGl и перейти к IwGx. Я ни в коем случае не настаиваю на том, что это единственное возможное решение, но мне оно показалось наиболее разумным.

Как обычно, начнем с mkb-файла:

arcanoid.mkb

#!/usr/bin/env mkb options {      module_path="../yaml"      module_path="../box2d" +    module_path="$MARMALADE_ROOT/examples" }  subprojects { -    iwgl +    iwgx +    iwresmanager +    SoundEngine      yaml      box2d }  includepath { 	./source/Main 	./source/Model } files { 	[Main] 	(source/Main) 	Main.cpp 	Main.h 	Quads.cpp 	Quads.h 	TouchPad.cpp 	TouchPad.h 	Desktop.cpp 	Desktop.h 	IO.cpp 	IO.h 	World.cpp 	World.h        +   ResourceManager.cpp +   ResourceManager.h  	[Model] 	(source/Model) 	IBox2DItem.h 	Board.cpp 	Board.h 	Bricks.cpp 	Bricks.h 	Ball.cpp 	Ball.h 	Handle.cpp 	Handle.h  +   [Data] +   (data) +   sounds.group }  assets {     (data)     level.json  +   (data-ram/data-gles1, data/data-gles1) +   sounds.group.bin } 

Загрузкой и воспроизведением ресурсов займется новый (и пока очень простой) модуль:

ResourceManager.h

#ifndef _RESOURCEMANAGER_H_ #define _RESOURCEMANAGER_H_  #include <map> #include <string>  #include "s3e.h" #include "IwResManager.h" #include "IwSound.h"  #define SOUND_GROUP "sounds"  using namespace std;  class ResourceManager { 	private: 		bool checkSound() {return true;} 	public: 		void init(); 		void release(); 		void update(); 		void fireSound(const char* name); };  extern ResourceManager rm;          #endif	// _RESOURCEMANAGER_H_ 

ResourceManager.cpp

#include "ResourceManager.h"  ResourceManager rm;  void ResourceManager::init() { 	IwSoundInit(); 	IwResManagerInit(); #ifdef IW_BUILD_RESOURCES 	IwGetResManager()->AddHandler(new CIwResHandlerWAV); #endif 	IwGetResManager()->LoadGroup("sounds.group"); }  void ResourceManager::release() { 	IwResManagerTerminate(); 	IwSoundTerminate(); }  void ResourceManager::update() {     IwGetSoundManager()->Update(); }  void ResourceManager::fireSound(const char* name) { 	if (!checkSound()) return;     CIwResGroup* resGroup = IwGetResManager()->GetGroupNamed(SOUND_GROUP); 	CIwSoundSpec* SoundSpec = (CIwSoundSpec*)resGroup->GetResNamed(name,                    IW_SOUND_RESTYPE_SPEC); 	CIwSoundInst* SoundInstance = SoundSpec->Play(); } 

Активировать звуковой эффект будем просто и без затей, из модуля World:

World.cpp

#include "s3e.h" +#include "ResourceManager.h" #include "World.h" #include "Ball.h" ... void World::PostSolve(b2Contact* contact, const b2ContactImpulse* impulse) { 	impact(contact->GetFixtureA()->GetBody()); 	impact(contact->GetFixtureB()->GetBody()); 	isContacted = true; +   rm.fireSound(CONTACT_SOUND); } 

Собственно это все изменения, которые было необходимо внести для реализации звуковых эффектов. Теперь нам предстоит внести ряд изменений, связанных с переходом с IwGl на IwGx.

Desktop.cpp

#include "Desktop.h" #include "IwGx.h" #include "s3e.h"  Desktop desktop;  void Desktop::init() {     IwGxInit();     IwGxLightingOff();     width = IwGxGetScreenWidth();     height = IwGxGetScreenHeight(); 	IwGxSetColClear(0, 0, 0, 0);     vSize = 0; 	duration = 1000 / 60; }  void Desktop::release() {     IwGxTerminate(); }  void Desktop::update() {     IwGxClear(IW_GX_COLOUR_BUFFER_F | IW_GX_DEPTH_BUFFER_F);     CIwMaterial* pMat = IW_GX_ALLOC_MATERIAL();     IwGxSetMaterial(pMat); }  void Desktop::refresh() {     IwGxFlush();     IwGxSwapBuffers();     s3eDeviceYield(duration);    }  int Desktop::toRSize(int x) const {     if (vSize == 0) return x;     return (x * width) / vSize; } 

Quads.h

#ifndef _QUADS_H_ #define _QUADS_H_  #include "IwGX.h"  #define MAX_VERTS 2000  class Quads {     private: 		CIwSVec2 verts[MAX_VERTS]; 		CIwColour cols[MAX_VERTS];         int     vertc;     public:         void update() {vertc = 0;}         void refresh(); 		void setVert(int x, int y, int r, int g, int b, int a); };  extern Quads quads;  #endif	// _QUADS_H_ 

Quads.cpp

#include "Quads.h" #include "s3e.h"  Quads quads;  void Quads::refresh() { 	IwGxSetVertStreamScreenSpace(verts, vertc);     IwGxSetColStream(cols, vertc);     IwGxDrawPrims(IW_GX_TRI_LIST, NULL, vertc); }  void Quads::setVert(int x, int y, int r, int g, int b, int a) { 	if (vertc >= MAX_VERTS) return; 	verts[vertc].x = x; 	verts[vertc].y = y;     cols[vertc].r  = r;  	cols[vertc].g  = g;  	cols[vertc].b  = b;  	cols[vertc].a  = a; 	vertc++; } 

Bricks.h

#ifndef _BRICKS_H_ #define _BRICKS_H_  #include "IwGX.h" #include "s3e.h"  #include "Desktop.h" #include "World.h" #include "IBox2DItem.h"  #define BRICK_HALF_WIDTH   20 #define BRICK_HALF_HEIGHT  10  #include <vector>  using namespace std;  class Bricks: public IBox2DItem {     private:         struct SBrick {             SBrick(int x, int y): x(x),                                            y(y),                                            body(NULL),  										  isBroken(false),                                           hw(BRICK_HALF_WIDTH),                                            hh(BRICK_HALF_HEIGHT) {}             SBrick(const SBrick& p): x(p.x),                                            y(p.y),                                            body(p.body),  										  isBroken(p.isBroken),                                           hw(p.hw),                                            hh(p.hh) {}             int x, y, hw, hh; 			int isBroken; 			b2Body* body;         };         vector<SBrick>* bricks; 		virtual bool impact(b2Body* b);     public:         Bricks(): bricks() {} 		void init(); 		void release();         void refresh();         void clear(){bricks->clear();}         void add(SBrick& b);      typedef vector<SBrick>::iterator BIter;      friend class Board; };  #endif	// _BRICKS_H_ 

Bricks.cpp

#include "Bricks.h" #include "Quads.h"  void Bricks::init() { 	bricks = new vector<SBrick>(); }  void Bricks::release() { 	delete bricks; }  void Bricks::refresh() {     for (BIter p = bricks->begin(); p != bricks->end(); ++p) { 		    if (p->isBroken) continue; 		    quads.setVert(p->x - p->hw, p->y - p->hh, 0x00, 0xff, 0x50, 0xff); 		    quads.setVert(p->x - p->hw, p->y + p->hh, 0x00, 0xff, 0x50, 0xff); 		    quads.setVert(p->x + p->hw, p->y - p->hh, 0x00, 0xff, 0x50, 0xff); 		    quads.setVert(p->x + p->hw, p->y + p->hh, 0x00, 0xff, 0x50, 0xff); 		    quads.setVert(p->x + p->hw, p->y - p->hh, 0x00, 0xff, 0x50, 0xff); 		    quads.setVert(p->x - p->hw, p->y + p->hh, 0x00, 0xff, 0x50, 0xff); 	} }  bool Bricks::impact(b2Body* b) {     for (BIter p = bricks->begin(); p != bricks->end(); ++p) { 		if (p->body == b) { 			p->isBroken = true; 			return true; 		} 	} 	return false; }  void Bricks::add(SBrick& b) {  	b.body = world.addBrick(b.x, b.y, b.hw, b.hh, (IBox2DItem*)this); 	bricks->push_back(b); } 

Здесь следует обращать внимание на порядок обхода вершин в треугольниках. Первым, что я увидел, после перехода с IwGl на IwGx, были угольно черные фигуры кирпичей и шарика на нежно пурпурном фоне. Я умудрился отобразить «с изнанки» абсолютно все треугольники, которые у меня были.

Второй важный момент касается работы с памятью. Я не знаю, как именно IwGx работает с памятью, но есть важное эмпирическое правило к кторому я пришел. Вся память выделенная динамически (в том числе и в STL) должна быть освобождена до момента вызова IwGxTerminate (в противном случае она будет разрушена). По этой причине, вектор bricks стал указателем на вектор. Возможно это не самое изящное решение, но оно влекло за собой минимальное количество необходимых изменений.

Аналогично изменяем остальные модули:

Ball.h

#ifndef _BALL_H_ #define _BALL_H_  #include <vector> #include "s3e.h"  #include "Desktop.h" #include "World.h" #include "IBox2DItem.h"  #define MAX_SEGMENTS       7 #define BALL_RADIUS        15  using namespace std;  class Ball: public IBox2DItem {     private:         struct Offset {             Offset(int dx, int dy): dx(dx), dy(dy) {}             Offset(const Offset& p): dx(p.dx), dy(p.dy) {}             int dx, dy;         };         vector<Offset>* offsets;         int      x;         int      y; 		b2Body*  body;     public:         void init();         void release();         void refresh(); 		virtual void setXY(int X, int Y);      typedef vector<Offset>::iterator OIter; };  #endif	// _BALL_H_ 

Ball.cpp

#include "Ball.h" #include "Desktop.h" #include "Quads.h" #include <math.h>  void Ball::init(){     x = desktop.getWidth() / 2;     y = desktop.getHeight()/ 2;  	offsets = new vector<Offset>();     float delta = - PI / (float)MAX_SEGMENTS;     float angle = delta / 2.0f;     float r = (float)desktop.toRSize(BALL_RADIUS);     for (int i = 0; i < MAX_SEGMENTS * 2; i++) {         offsets->push_back(Offset((int16)(cos(angle) * r), (int16)(sin(angle) * r)));         angle = angle + delta;         offsets->push_back(Offset((int16)(cos(angle) * r), (int16)(sin(angle) * r)));     } 	body = world.addBall(x, y, (int)r, (IBox2DItem*)this); }  void Ball::release() { 	delete offsets; }  void Ball::setXY(int X, int Y) { 	x = X; 	y = Y; }  void Ball::refresh() {     OIter o = offsets->begin();     int r = desktop.toRSize(BALL_RADIUS);     for (int i = 0; i < MAX_SEGMENTS * 2; i++) { 		    quads.setVert(x + (r / 4), y + (r / 4), 0xff, 0xff, 0xff, 0xff); 		    quads.setVert(x + o->dx, y + o->dy, 0x00, 0x00, 0x00, 0x00); 			o++; 		    quads.setVert(x + o->dx, y + o->dy, 0x00, 0x00, 0x00, 0x00); 			o++; 	} } 

Handle.h

#ifndef _HANDLE_H_ #define _HANDLE_H_  #include "IwGX.h" #include "s3e.h"  #include "Desktop.h" #include "World.h" #include "IBox2DItem.h"  #define HANDLE_COLOR        0xffff3000 #define HANDLE_H_WIDTH      40 #define HANDLE_H_HEIGHT     10  class Handle: public IBox2DItem { 	private:         int     x;         int     y; 		int     touchId; 	public:         void init();         void refresh();         void update(); 		virtual void setXY(int X, int Y); };  #endif	// _HANDLE_H_ 

Handle.cpp

#include "Handle.h" #include "Quads.h" #include "TouchPad.h"  void Handle::init() {     x = desktop.getWidth() / 2;     y = desktop.getHeight(); 	touchId = -1; }  void Handle::setXY(int X, int Y) { 	x = X; 	y = Y; }  void Handle::refresh() {  	int hw = desktop.toRSize(HANDLE_H_WIDTH); 	int hh = desktop.toRSize(HANDLE_H_HEIGHT);      quads.setVert(x - hw, y - hh, 0x00, 0x30, 0xff, 0xff);     quads.setVert(x - hw, y + hh, 0x00, 0x30, 0xff, 0xff);     quads.setVert(x + hw, y - hh, 0x00, 0x30, 0xff, 0xff);     quads.setVert(x + hw, y + hh, 0x00, 0x30, 0xff, 0xff);     quads.setVert(x + hw, y - hh, 0x00, 0x30, 0xff, 0xff);     quads.setVert(x - hw, y + hh, 0x00, 0x30, 0xff, 0xff);  	world.addHandle(x, y, desktop.toRSize(HANDLE_H_WIDTH),                            desktop.toRSize(HANDLE_H_HEIGHT), (IBox2DItem*)this); }  void Handle::update() { 	Touch* t = NULL; 	if (touchId > 0) { 		t = touchPad.getTouchByID(touchId); 	} else { 		t = touchPad.getTouchPressed(); 	} 	if (t != NULL) { 		touchId = t->id; 		world.moveHandle(t->x, t->y); 	} else { 		touchId = -1; 	} } 

В Board мы просто заменяем два вектора (scopes и types) указателями на вектора. Далее, запускаем приложение и убеждаемся, что все работает:

image

Можно заметить, что я убрал градиентную заливку с кирпичей. Честно говоря, было просто лень с этим возиться, с учетом того, что все равно, на все объекты будут натягиваться текстуры (в противном случае, о каком либо товарном виде не стоит даже мечтать).

Именно отображением текстур я и планирую заняться в следующий раз.

ссылка на оригинал статьи http://habrahabr.ru/post/169435/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *