После небольшого перерыва, продолжим нашу разработку. Сегодня мы добавим в проект небольшой звуковой эффект, проигрываемый при соударении шарика с чем либо на игровом поле. О работе с SoundEngine (которой мы сегодня воспользуемся) я уже писал ранее. По этой причине, сегодня я расскажу не столько о ней, сколько о том, как ее использование отразится на разрабатываемом нами проекте.
Главным (и несколько неожиданным для меня) последствием включения в проект SoundEngine явилась необходимость перехода с IwGl на IwGx. Дело в том, что SoundEngine использует для загрузки описаний звуковых эффектов возможности подсистемы IwResManager. Само описание выглядит следующим образом:
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 приводит к тому, что он просто перестает собираться:
Добавление в проект IwGeom также мало помогает:
После бесплодных попыток исправить ситуацию, я решил отказаться от использования IwGl и перейти к IwGx. Я ни в коем случае не настаиваю на том, что это единственное возможное решение, но мне оно показалось наиболее разумным.
Как обычно, начнем с 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
}
Загрузкой и воспроизведением ресурсов займется новый (и пока очень простой) модуль:
#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_
#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:
#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.
#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;
}
#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_
#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++;
}
#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_
#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 стал указателем на вектор. Возможно это не самое изящное решение, но оно влекло за собой минимальное количество необходимых изменений.
Аналогично изменяем остальные модули:
#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_
#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++;
}
}
#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_
#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) указателями на вектора. Далее, запускаем приложение и убеждаемся, что все работает:
Можно заметить, что я убрал градиентную заливку с кирпичей. Честно говоря, было просто лень с этим возиться, с учетом того, что все равно, на все объекты будут натягиваться текстуры (в противном случае, о каком либо товарном виде не стоит даже мечтать).
Именно отображением текстур я и планирую заняться в следующий раз.
Автор: GlukKazan