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

в 5:22, , рубрики: android, iOS, marmalade, Разработка под android, разработка под iOS, метки: , ,

После небольшого перерыва, продолжим нашу разработку. Сегодня мы добавим в проект небольшой звуковой эффект, проигрываемый при соударении шарика с чем либо на игровом поле. О работе с 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

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

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

Автор: GlukKazan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js