📜 ⬆️ ⬇️

Little Brave Arkanoid (Part 4)

After a short break, we will continue our development . Today we will add a small sound effect to the project, which is played when a ball collides with anything on the playing field. About work with SoundEngine (which we will use today) I already wrote earlier . For this reason, today I will tell not so much about it, but about how its use will affect the project we are developing.

The main (and somewhat unexpected for me) consequence of including SoundEngine in the project was the need to switch from IwGl to IwGx. The fact is that SoundEngine uses IwResManager subsystem capabilities for loading sound effect descriptions. The description itself is as follows:

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" } } 


Attempting to include in the project IwResManager leads to the fact that it simply stops gathering:


')
Adding IwGeom to a project also doesn't help much:



After fruitless attempts to correct the situation, I decided to stop using IwGl and move on to IwGx. I do not insist that this is the only possible solution, but it seemed to me the most reasonable.

As usual, let's start with the mkb file:

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 } 


Downloading and playback of resources will take a new (and still very simple) module:

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(); } 


Activate the sound effect will be simple and simple, from the World module:

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); } 


Actually these are all the changes that were necessary to make for the realization of sound effects. Now we have to make a number of changes related to the transition from IwGl to 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(px), y(py), 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(bx, by, b.hw, b.hh, (IBox2DItem*)this); bricks->push_back(b); } 


Here you should pay attention to the order of traversing vertices in triangles. The first thing I saw, after switching from IwGl to IwGx, were the coal-black figures of bricks and a ball on a soft purple background. I managed to display "from the inside out" absolutely all the triangles that I had.

The second important point concerns working with memory. I do not know how exactly IwGx works with memory, but there is an important rule of thumb to which I came. All memory allocated dynamically (including in STL) must be freed until the moment IwGxTerminate is called (otherwise it will be destroyed). For this reason, the vector bricks has become a pointer to a vector. Perhaps this is not the most elegant solution, but it entailed the minimum amount of necessary changes.

Similarly, we modify other modules:

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; } } 


In Board, we simply replace two vectors (scopes and types) with pointers to vectors. Next, run the application and make sure that everything works:



You may notice that I removed the gradient fill from the bricks. Honestly, it was just too lazy to mess around with it, given that all the same, textures will be stretched on all objects (otherwise, you shouldn’t even dream of any presentation).

Source: https://habr.com/ru/post/169435/


All Articles