📜 ⬆️ ⬇️

How I saved a few lives with optimization and a little about working at Zeptolab

Hello!

23derevo before speaking at Mobius asked me to talk a little about the client development process in Zeptolab.


')
To begin with, we write in C ++ and in our framework; from any client device, we need only the OpenGL context. Then we build our interface from scratch, our controls and so on. Accordingly, in order to take a developer into a team, in theory, it is enough for him to know the advantages. In practice, this is a bit wrong.



About work


I came to Zeptolab even when we had three developers: CTO, iOS-developer and Android-developer. Before that, I studied at ShAD Yandex and, in parallel with work, sawed the base of customs documentation with the possibility of rich formatting, storing files and images - in general, a kind of MSDN, only for customs needs. Until now, it is used, and until now, it is just beginning to find analogs.

I had no super cool technological knowledge, I was engaged in graphics, wrote small projects on OpenGL, did shaders. This, in general, was enough to start learning from the mobile development branch.

In my opinion, the most important thing for a candidate (and a developer in general) is general intelligence, technical outlook and technical thinking. By the way, at the interview I was asked the notorious task about round hatches. Now I myself conduct interviews, and give similar abstract tasks. Oskomina they fill some candidates due to the fact that the list of such tasks rarely changes (if given randomly, there will not be a common metric - it will not be very fair to the candidates). But, given that they are “leaking” beyond the limits of interviews, we usually prepare a couple more of our tasks to check if the candidate does not read. If the logic is all right, then ignorance of some syntactic features of the language is a problem of a smaller scale. You can learn the syntax, programming patterns are also being studied, but, alas, you need to think right away.

In general, the coder differs from the developer in that the latter is able to come up with ideas for solving problems. In one well-known large company my good friend works. Their Russian office is busy only by inventing algorithms, checking them on Python or C # for prototypes, and then giving the results to the units in India and China. There, already far-away coders without fantasy, but with the utmost pedantry and with purely Asian persistence, take the described ideas and ideally implement them in the code for C ++ or C microcontrollers for each device.

I would advise those who are looking for work immediately after the university to get a rating in the region of 2000 on Codeforces. If you are slightly yellow there, this is a high chance to go, for example, in Google. In addition, you will quickly understand that the first place is the ability to think and solve unique problems, when specific technologies are already being studied “locally” to the required (or sufficient) level.

A few words about the artists


At first we had Cocos2D. A good framework, but many things simply did not suit us for implementation, so we began to write our system. Quickly enough, I managed to implement a very cool animation system and good resource preparation in C ++. We have already told about the animation, if briefly - it is prepared in Flash, then we parse the FLA-files, and then we recreate the same animations in the application. The most important thing for us has always been the emphasis on quality. In the case of animations, this is smooth: artists often stand behind the programmers and say what’s wrong. Without training, you can not see where and what slightly twitches, but the artists just feel it. An ordinary person will not be able to understand what is wrong, and will not always be able to describe it, even if he sees it. But it will feel, often not very consciously, that it is “rough.” Our artists achieve the perfect picture for themselves, and they know how to explain in technical terms what needs to be changed.



At the conference, our guys will tell you exactly how we achieved this quality of picture and show that under the hood of the framework. I will tell about the evolution of our technologies, about the preparation of resources. It is very important for controls to get exactly a pixel to a pixel, to prepare fonts correctly, to take into account low-res devices and much more. Again, I will show concrete examples at the conference.



For me, perhaps the greatest thrill is to stand behind the artist and watch him create a masterpiece out of nothing. Sometimes they also look at us and try to understand what we are doing. It seems to us in development that there are few good artists on the market, and it’s hard to find such cool ones. It seems to them that developers who understand what they need are unrealistically few.



By the way, with all their sincere humanitarian education, there have never been any problems with the technical part. The tasks are formulated perfectly, the general architecture is presented. There was even such a funny case: we photographed for a contest on Codeforces we are an artist in the role of developer, for fun. Glasses, complex face, thought. So, after that, he suddenly began to write code in JavaScript. At first there were very simple macros for Photoshop and Flash. Then in a few months, in fact, went through the whole history of the evolution of the development, discovering new and new possibilities. I remember that at some point he came up and began to clumsily explain a concept that would help him write complex scripts: after a while I realized that he wanted to set breakpoints and watch the values ​​of variables. Himself came to the use of assertions. Prior to that, we sometimes laughed at his code: he interfered with expressions in one line, without indents, it looked really a little crazy. And then somehow quietly began to make very cool scripts. Now we think who else to photograph with a complex face.
But back to the framework. We have quite a lot of routine, in particular, associated with its constant modifications. The framework develops, a new hardware appears, new requirements, I want to deal with the Legacy code in a timely manner. Of the last major tasks, for example, we needed our own particle system. We looked at what is in Unity, the artists say - megakruto, but you need more of this, this and this.

As a result, the task has been reduced not only to writing a particle generator, but also to the implementation of a user-friendly interface. We have several layers of emission, and the particles move according to different laws. An arbitrary formula for each would very heavily load client devices (in fact, it would have to parse each one separately), and the overall one was not flexible enough to implement the artists' ideas. It was decided by mathematics - they derived some general formula for each particle, where by changing the coefficients you can run even a parabola, even a sinusoid. And it does not slow down, and there is visual wealth.

In a team



Every two weeks we teach ourselves. The guys (and now we have 21 developers in the team) are learning something new, which they have not yet used on other projects, or which are not in other companies. They gather everyone, they say that they have found something interesting. It can be a variety of topics: starting from how to make the download subjectively fast for the user and ending with a quick background blur behind popup (as done in King of Thieves). Mikhail Mirzayanov regularly came to our office (by the way, he coached our team, which won first place at ACM / ICPC). I read 3 blocks of the coolest lectures on algorithms and data structures, showed rare, little-known structures and tasks (for example, about a segment tree, which he independently discovered as one of the first in the world and the first in Russia). As a training, we went to a three-day training session by Scott Myers (who wrote this book "Effective Modern C ++").



Of the examples of problems, in 2013 a rather large article was published on the solutions of the well-known NP-hard problem on the packaging of texture atlases. According to the results of one of the Codeforces contests, a strong algorithmist came to us. I read the article, I thought for a long time, then I wrote my own algorithm, an improved version of the well-known, which we immediately checked on one of the most difficult packaged atlases. If we take for 100% perfect packaging, then our previous algorithm gave more than 120% result, and the new one on the same data set began to show 104%. In practice, this means a reduction in memory consumption per megabytes.

In general, for 500 million installations such things look very funny. For example, our very first system for storing and loading images operated on PNG files, and it took about 15 seconds to load the level on the test device. Profiled, sorted out - PNG decoding took most of the time. I rewrote this code (it took a new internal graphics storage format) - and on the same test device, the download took 6 seconds. 9 seconds saved - we rolled out a new storage system for all our games. If you count the 20 downloads of the game for some basic indicator, it seems to me that this saved at least fifty lives. Then this mechanism was accelerated by another 20-30% on the advice of a beginner, who did the same at the old place of work, because at some point simple calculations on the processor ceased to be a bottleneck of the loading system, everything began to rest on the reading speed from the storage. . Improved your format.
Optimization is generally a lot of work. Our games even work on old hardware, the framework supports iOS 4.3 (now iOS 5: initially because of the partner code, then we began to use libc ++, which is also available from iOS 5, in the second version of the framework). We are developing entirely new applications and experiments under the top models, because by the end of the development they will become the most popular device - but we don’t forget the “oldies”. With the same "Cut the Rope" most of the release is content updates. The old code is not broken. New games are much richer visually, but the requirements for the hardware are higher.

We do prototyping very quickly, faster than in many studios. A game designer issues a concept, then in 1-2 days one of the developers does a “dream job” - collects a prototype from scratch without graphics, on primitives. If jumping the ball and the square after that pins the game designer - goes to work further. Naturally, the prototypes are much smaller than the usual tasks, but we are trying to ensure that everyone in the team sooner or later wrote his own.

Again, of course, we do it right away on the finished blank project, where all the basic things are. For people coming from the development of native mobile applications, this is just another world - there are no standard controllers, their own preparation of resources, in general, all of their own and not even particularly tied to the platform. Those who worked with Unity are more interested in digging “under the hood”, seeing the realization of some things that are difficult to do there. With Coconut, in general, there are parallels at a high level, but it is still interesting to make out the game and see how it works inside.

A little about the test



Finally - my little argument with friends. Below under the spoiler 5 sample code from test tasks from different people. The code is published with the consent of all candidates. (careful, the sources are quite large)

App.cpp
// // App.cpp // Asteroids // // Created by xxxx // // #include <string> #include "App.h" #include "RenderCommandPolygonConvex.h" #include "Vec2.h" #include "Color.h" #include "GameMap.h" #include "Camera.h" #include "MapDrawObjectPolygon.h" #include "MapObjectMovable.h" #include "IMovable.h" #include "MovableObjectTouch.h" #include "MapObjectEmitter.h" #include "EmitterLineContinuous.h" #include "MovableInDirection.h" #include "MapObjectHero.h" #include "MapObjectAsteroid.h" #include "MapObjectDebris.h" const float LOGIC_MAP_WIDTH = 100; const float GAMEPLAY_ACCELERATION = 0.003; namespace { void initAsteroidsEmitters(GameMapPtr gameMap, float logicMapWidth, std::vector<EmitterLineContinuousPtr>& asteroidEmitters) { for_each(asteroidEmitters.begin(), asteroidEmitters.end(), [](EmitterLineContinuousPtr emitter){emitter->die();}); MapObjectEmitterPtr emitterMapObject(new MapObjectEmitter()); EmitterLineContinuousPtr emitter(new EmitterLineContinuous(Vec2(-logicMapWidth*0.5f, 0), Vec2(logicMapWidth*1.5f, 0), Vec2(0, 0), 8, 25, -1, gameMap)); emitter->setParticlesMapObject(MapObjectAsteroidPtr(new MapObjectAsteroid())); asteroidEmitters.push_back(emitter); emitterMapObject->setEmitter(emitter); gameMap->addMapObject(emitterMapObject, Vec2(0, -10), 0); MapObjectEmitterPtr emitterMapObject2(new MapObjectEmitter()); EmitterLineContinuousPtr emitter2(new EmitterLineContinuous(Vec2(0, 0), Vec2(logicMapWidth, 0), Vec2(0, 1), 1, 30, -1, gameMap)); emitter2->setParticlesMapObject(MapObjectAsteroidPtr(new MapObjectAsteroid())); emitterMapObject2->setEmitter(emitter2); asteroidEmitters.push_back(emitter2); gameMap->addMapObject(emitterMapObject2, Vec2(0, -10), 0); MapObjectEmitterPtr emitterMapObject3(new MapObjectEmitter()); EmitterLineContinuousPtr emitter3(new EmitterLineContinuous(Vec2(0, 0), Vec2(logicMapWidth, 0), Vec2(0, 1), 3, 20, -1, gameMap)); emitter3->setParticlesMapObject(MapObjectDebrisPtr(new MapObjectDebris())); emitterMapObject3->setEmitter(emitter3); asteroidEmitters.push_back(emitter3); gameMap->addMapObject(emitterMapObject3, Vec2(0, -10), 0); MapObjectEmitterPtr emitterMapObject4(new MapObjectEmitter()); EmitterLineContinuousPtr emitter4(new EmitterLineContinuous(Vec2(0, 0), Vec2(logicMapWidth, 0), Vec2(0, 1), 1, 40, -1, gameMap)); emitter4->setParticlesMapObject(MapObjectAsteroidPtr(new MapObjectAsteroid())); emitterMapObject4->setEmitter(emitter4); asteroidEmitters.push_back(emitter4); gameMap->addMapObject(emitterMapObject4, Vec2(0, -10), 0); } } App::App() :time_(0) { } void App::updateAndRender(float dtSec, std::vector<RenderCommandBasePtr>& renderCommands) { update(dtSec); collectRenderData(renderCommands); } bool App::touch(const std::vector<TouchEvent>& events) const { if (events.empty()) return false; if (gameMap_) gameMap_->touch(events); return true; } void App::update(float dtSec) { if (gameMap_) gameMap_->update(dtSec); tryRespawnHero(); updateGameplayAcceleration(); time_+= dtSec; } void App::resetGameplay() { time_ = 0; ::initAsteroidsEmitters(gameMap_, LOGIC_MAP_WIDTH, asteroidEmitters_); } void App::tryRespawnHero() { if (!hero_ || hero_->isReadyToDestruct() ) { if (!gameMap_->hasObjectOfType(MAP_OBJECT_HERO_DEBRIS)) { resetGameplay(); hero_ = MapObjectHeroPtr(new MapObjectHero(Rect(0, 0, logicMapSize_.x, logicMapSize_.y))); gameMap_->addMapObject(hero_, Vec2(50, logicMapSize_.y - 10), 0); } } } void App::setScreenSize(int screenW, int screenH) { screenSize_ = Vec2(screenW, screenH); float logicCellSize = screenW/LOGIC_MAP_WIDTH; logicMapSize_ = Vec2(screenW/logicCellSize, screenH/logicCellSize); CameraPtr camera = CameraPtr(new Camera(logicCellSize, logicCellSize)); gameMap_ = GameMapPtr(new GameMap(Size(logicMapSize_.x, logicMapSize_.y), camera)); gameMap_->setLiveAreaRect(Rect(-logicMapSize_.x/2, -10, logicMapSize_.x*2, logicMapSize_.y + 20)); resetGameplay(); } void App::collectRenderData(std::vector<RenderCommandBasePtr>& renderCommands) const { gameMap_->collectRenderData(renderCommands); } void App::updateGameplayAcceleration() { for (auto emitter: asteroidEmitters_) { emitter->setSpeedParticles(emitter->getSpeedParticles() + time_*GAMEPLAY_ACCELERATION); } } 


game.cpp
 /* w,a,d    r   , space  */ #include "stdafx.h" #include <math.h> #include <vector> #include <iostream> #include <fstream> #include <glut.h> using namespace std; const float Pi=3.14159265358; float winwid=400; float winhei=400; bool game_end=0; /////bullet//// float dx=0,dy=0; float bull_speed=6; float betta=0; bool fl1=0, fl2=0; /////ship//// float speed=0; float angle=0; float acsel=0; /////asteroid///// float ast_size=50; float aster_speed=3; /////0-rand//// int kol_aster=0; class bullet { public: float dxb; float dyb; float angleb; bullet() { dxb=dx; dyb=dy; angleb=betta; } }; class asteroid { public: float anglea; float dx; float dy; float depth; int n; int i_big; int ifsmall; vector <double> x; vector <double> y; void create(int i,bool param); void create_small(int i,int j,bool param,float depth1,float dx1,float dy1); }; void asteroid:: create_small(int i,int j,bool param,float depth1,float dx1,float dy1) { ifsmall=0; int size=ast_size/2; depth=depth1+(j+2)*1.0/(8.0*(kol_aster)); dx=dx1; dy=dy1; i_big=i; ///////////////////////////////////////////////// int quat=rand()%4; int n1=rand()%2+1; int n2=rand()%2+1; int n3=rand()%2+1; int n4=rand()%2+1; n1=n2=n3=n4=1; n=n1+n2+n3+n4; double xi,yi; anglea=rand()%360; x.clear(); y.clear(); for (int i=0;i<n1;i++) { xi=rand()%(size/2)-size/2; yi=rand()%(size/2)+size/2; x.push_back(xi); y.push_back(yi); } for (int i=0;i<n2;i++) { xi=rand()%(size/2)+size/2; yi=rand()%(size/2)+size/2; x.push_back(xi); y.push_back(yi); } for (int i=0;i<n3;i++) { xi=rand()%(size/2)+size/2; yi=rand()%(size/2)-size/2; x.push_back(xi); y.push_back(yi); } for (int i=0;i<n4;i++) { xi=rand()%(size/2)-size/2; yi=rand()%(size/2)-size/2; x.push_back(xi); y.push_back(yi); } ////////////////////////////////////////////////// } void asteroid:: create(int kol_exist,bool param) { int size=ast_size; int quat=rand()%4; int n1=rand()%2+1; int n2=rand()%2+1; int n3=rand()%2+1; int n4=rand()%2+1; n1=n2=n3=n4=1; n=n1+n2+n3+n4; double xi,yi; anglea=rand()%360; i_big=kol_exist; ifsmall=1; depth=(float)(kol_exist)/((kol_aster)); dx=rand()%(int)winwid -winwid/2; dy=rand()%(int)winhei -winhei/2; if(quat==0) dy=-ast_size-winhei/2; if(quat==1) dy=ast_size+winhei/2; if(quat==2) dx=-ast_size-winwid/2; if(quat==3) dx=ast_size+winwid/2; x.clear(); y.clear(); for (int i=0;i<n1;i++) { xi=rand()%(size/2)-size/2; yi=rand()%(size/2)+size/2; x.push_back(xi); y.push_back(yi); } for (int i=0;i<n2;i++) { xi=rand()%(size/2)+size/2; yi=rand()%(size/2)+size/2; x.push_back(xi); y.push_back(yi); } for (int i=0;i<n3;i++) { xi=rand()%(size/2)+size/2; yi=rand()%(size/2)-size/2; x.push_back(xi); y.push_back(yi); } for (int i=0;i<n4;i++) { xi=rand()%(size/2)-size/2; yi=rand()%(size/2)-size/2; x.push_back(xi); y.push_back(yi); } } vector <bullet> vecb; vector <asteroid> veca; void destroy_small_ast( int i) { //////  4 -  ///// bool create_big=1; float up_boarder=(float)(veca[i].i_big)/((kol_aster)); float down_boarder=(float)(veca[i].i_big)/((kol_aster)); asteroid a_big; a_big.create(veca[i].i_big,1); if (i>0) if(veca[i-1].depth>down_boarder) create_big=0; if (i<veca.size()-1) if(veca[i+1].depth<up_boarder) create_big=0; {if (create_big==1) {veca.insert(veca.begin()+veca[i].i_big,a_big); veca[veca[i].i_big].create(veca[i].i_big,1); veca.erase(veca.begin()+i+1);} else veca.erase(veca.begin()+i);} } void destroy_aster(float dep) { dep=1-2*dep; for(int i=0;i<veca.size();i++) { if (abs(dep-veca[i].depth)<0.0001) {if(veca[i].ifsmall==1) { veca.resize(veca.size()+4); for(int j=0;j<4;j++) veca[veca.size()-j-1].create_small(i,j,0,veca[i].depth,veca[i].dx,veca[i].dy); veca.erase(veca.begin()+i); break; } else {destroy_small_ast( i);break;} } } } void shoot() { float depth[5]; for(int i=0;i<vecb.size();i++) { glLoadIdentity(); ////// ////// vecb[i].dxb+=(speed+bull_speed)*cos(Pi*(vecb[i].angleb)/180.0); vecb[i].dyb+=(speed+bull_speed)*sin(Pi*(vecb[i].angleb)/180.0); ////  ///// if((vecb[i].dxb>winwid/2-1) ||(vecb[i].dxb<-winwid/2+1) ||(vecb[i].dyb<-winhei/2+1) || (vecb[i].dyb>winhei/2-1)) {vecb.erase(vecb.begin()+i);i--;} else{ ///// //// glReadPixels((vecb[i].dxb+winwid/2),-vecb[i].dyb+winhei/2,2,2,GL_DEPTH_COMPONENT,GL_FLOAT,depth); if (depth[0]!=1) { destroy_aster(depth[0]); vecb.erase(vecb.begin()+i); i--; } else { ////// ////// glTranslatef(vecb[i].dxb,vecb[i].dyb,0.0f); glColor3f(1.0f,1.0f,1.0f); glBegin(GL_LINES); glVertex3f( 0.0,0.0, 0.5f); glVertex3f(1.0,0.0, 0.5f); glEnd(); } } } } void aster_draw() { glColor3f(0.5f,1.0f,1.0f); glLoadIdentity(); for (int i=0;i<veca.size();i++) { glBegin(GL_POLYGON); for(int j=0;j<veca[i].n;j++) glVertex3f( veca[i].dx+veca[i].x[j],veca[i].dy+veca[i].y[j], veca[i].depth); glEnd(); veca[i].dx+=aster_speed*cos(Pi*(veca[i].anglea)/180.0); veca[i].dy+=aster_speed*sin(Pi*(veca[i].anglea)/180.0); /////    ,   /// if((veca[i].dx>winwid/2+ast_size) ||(veca[i].dx<-winwid/2-ast_size) ||(veca[i].dy<-winhei/2-ast_size) || (veca[i].dy>winhei/2+ast_size)) if (veca[i].ifsmall==0) {destroy_small_ast( i);i--;} else veca[i].create(i,1); } } void asteroidsinit() { int k; k=rand()%6+4; if(kol_aster!=0) k=kol_aster; else kol_aster=k; veca.resize(k); for(int i=0;i<k;i++) veca[i].create(i,1); } void draw_ship() { float depth[6]; float dx1,dx2,dy1,dy2; ////////   ///// if ((dx<winwid/2-1)&&(dx>-winwid/2+1)&&(dy<winwid/2-1)&&(dy>-winwid/2+1)) glReadPixels((dx+winwid/2),-dy+winhei/2,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,depth); else depth[0]=1; dx1=dx-10*cos(Pi*betta/180)-10*sin(Pi*betta/180)+winwid/2; dy1=-dy+10*sin(Pi*betta/180)-10*cos(Pi*betta/180)+winhei/2; if ((dx1<winwid-1)&&(dx1>0+1)&&(dy1<winhei-1)&&(dy1>0+1)) glReadPixels(dx1,dy1,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,depth+1); else depth[1]=1; dx2=dx-10*cos(Pi*betta/180)+10*sin(Pi*betta/180)+winwid/2; dy2=-dy+10*sin(Pi*betta/180)+10*cos(Pi*betta/180)+winhei/2; if ((dx2<winwid-1)&&(dx2>0+1)&&(dy2<winhei-1)&&(dy2>0+1)) glReadPixels(dx2,dy2,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,depth+2); else depth[2]=1; ///////   //// if(dx>winwid/2) dx=-winwid/2; if(dx<-winwid/2) dx=winwid/2; if(dy<-winhei/2) dy=winhei/2; if(dy>winhei/2) dy=-winhei/2; /////////////// glColor3f(0.8f,0.0f,0.8f); glLoadIdentity(); glTranslatef(dx,dy,0.0f); glRotatef(betta,0.0f,0.0f,1.0f); glBegin(GL_TRIANGLES); glVertex3f( -10.0f,-10.0f, 1.0f); glVertex3f(-10.0f,10.0f, 1.0f); glVertex3f(0.0f,0.0f, 1.0f); if (fl2==1){ glVertex3f( -10.0f,-3.0f, 1.0f); glVertex3f(-10.0f,3.0f, 1.0f); glVertex3f(-15.0f,0.0f, 1.0f); } glEnd(); ///////// - ///////// if ((depth[0]!=1)||(depth[1]!=1)||(depth[2]!=1)) game_end=1; } void display() { glClearDepth( 1.0f ); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); aster_draw(); draw_ship(); shoot(); glutSwapBuffers(); } void Timer(int) { acsel--; if(speed>10) speed=10; if (fl1==1) {angle=betta;fl1=0;} if (acsel==0) {fl2=0;} dx=dx+speed*cos(Pi*angle/180.0); dy=dy+speed*sin(Pi*angle/180.0); if(speed>0)speed=speed-0.1; else speed=0; display(); if(game_end==0) glutTimerFunc(50,Timer,0); } void Initialize() { dx=0; dy=0; vecb.empty(); angle=betta=speed=0; glClearColor(0, 0, 0.0, 1.0); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-winwid/2, winwid/2, winhei/2, -winhei/2, -1, 1); glMatrixMode(GL_MODELVIEW); glEnable(GL_DEPTH_TEST); glDepthFunc( GL_LEQUAL ); float depth[5]; glClearDepth( 1.0f ); //     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); asteroidsinit(); glutTimerFunc(500,Timer,0); } void keyboard(unsigned char key,int x,int y) { if (key=='w') {fl1=1;speed++;fl2=1;acsel=10;} if (key=='d') {betta+=7;} if (key=='a') betta-=7; if (key==' ') {bullet b1;vecb.push_back(b1);} if(key=='r') {if(game_end==1) {game_end=0;Initialize();}} } int main(int argc, char **argv)//  { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DEPTH |GLUT_DOUBLE | GLUT_RGB); glutInitWindowSize(winwid, winhei); glutInitWindowPosition(200, 200); glutCreateWindow("Powder Toy"); Initialize(); glutDisplayFunc(display); glutKeyboardFunc(keyboard); glutMainLoop(); } 


game.cpp
 #include "game.h" #include "logic.h" Game::Game(unsigned width, unsigned height) : _asteroids(std::vector<AsteroidFamily *>()), _shots(std::vector<Shot *>()), _booms(std::vector<Boom *>()), _score(0), _livesBonus(10000), _level(0), _isAsteroidsEmpty(true), _gameOver(false), _playerPoints(new std::vector<Point>(3)), _shotsPoints(new std::vector<Point>(4)), _boomPoints(new std::vector<Point>(8)), _lastTimepoint(std::chrono::high_resolution_clock::now()), _lastShotTimepoint(std::chrono::high_resolution_clock::now()), _gameOverTimepoint(std::chrono::high_resolution_clock::now()), _timeMultiplier(0.0f), _width(width), _height(height), _aspectRatio((float)width / (float)height), _render(new GL(&_aspectRatio, &_halfWidth, &_halfHeight)), _controls(new Controls(&_width, &_height, &_halfWidth, &_halfHeight)) { _halfHeight = GAME_HEIGHT; _halfWidth = _aspectRatio * _halfHeight; _playerPoints = new std::vector<Point>(3); (*_playerPoints)[0].x = -0.6f; (*_playerPoints)[0].y = -0.5f; (*_playerPoints)[1].x = -0.6f; (*_playerPoints)[1].y = 0.5f; (*_playerPoints)[2].x = 0.6f; (*_playerPoints)[2].y = 0.0f; _shotsPoints = new std::vector<Point>(4); (*_shotsPoints)[0].x = 0.02f; (*_shotsPoints)[0].y = 0.02f; (*_shotsPoints)[1].x = 0.02f; (*_shotsPoints)[1].y = -0.02f; (*_shotsPoints)[2].x = -0.02f; (*_shotsPoints)[2].y = -0.02f; (*_shotsPoints)[3].x = -0.02f; (*_shotsPoints)[3].y = 0.02f; _boomPoints = new std::vector<Point>(8); (*_boomPoints)[0].x = 0.1f; (*_boomPoints)[0].y = 0.1f; (*_boomPoints)[1].x = 0.5f; (*_boomPoints)[1].y = 0.4f; (*_boomPoints)[2].x = -0.1f; (*_boomPoints)[2].y = -0.2f; (*_boomPoints)[3].x = -0.5f; (*_boomPoints)[3].y = -0.4f; (*_boomPoints)[4].x = 0.2f; (*_boomPoints)[4].y = -0.1f; (*_boomPoints)[5].x = 0.5f; (*_boomPoints)[5].y = -0.5f; (*_boomPoints)[6].x = -0.1f; (*_boomPoints)[6].y = 0.2f; (*_boomPoints)[7].x = -0.5f; (*_boomPoints)[7].y = 0.5f; Shot::SetStaticPoints(_shotsPoints); Boom::SetStaticPoints(_boomPoints); Random::Init(&_halfWidth, &_halfHeight); _player = new Player(_playerPoints, 0.0f, 0.0f); } Game::~Game() { delete _player; delete _render; for (Shot *item : _shots) delete item; for (Boom *item : _booms) delete item; for (AsteroidFamily *item : _asteroids) delete item; delete _playerPoints; delete _shotsPoints; delete _boomPoints; } void Game::Refresh() { _controls->Refresh(); if (_score >= _livesBonus) { _livesBonus += 10000; _player->SetLives(_player->GetLives() + 1); } if (_isAsteroidsEmpty) { _level++; for (AsteroidFamily *item : _asteroids) delete item; _asteroids.clear(); for (unsigned i = 0; i < (_level + 1) * 2; ++i) _asteroids.push_back(Random::GenerateAsteroidFamily()); _isAsteroidsEmpty = false; } _isAsteroidsEmpty = true; std::chrono::high_resolution_clock::time_point now = std::chrono::high_resolution_clock::now(); auto time_span = std::chrono::duration_cast<std::chrono::nanoseconds>(now - _lastTimepoint).count(); _timeMultiplier = (float)time_span / 16666666.67f; _lastTimepoint = now; if (_controls->GetHyperspace()) { if (!_gameOver) { Random::ChangePlayerCoords(_player); _controls->SetHyperspace(false); } else { if (std::chrono::duration_cast<std::chrono::milliseconds>(now - _gameOverTimepoint).count() >= GAMEOVER_SCORE_TIME) { _player->SetCoord(0.0f, 0.0f); _player->SetAngle(0.0f); _player->SetLives(PLAYER_DEFAULT_LIVES); for (AsteroidFamily *item : _asteroids) delete item; _asteroids.clear(); _isAsteroidsEmpty = true; _score = 0; _level = 0; _livesBonus = 10000; _gameOver = false; _controls->SetHyperspace(false); } } } if (_player->GetIsGhost() && !_gameOver) { if (!_player->GetIsRendering()) { if (std::chrono::duration_cast<std::chrono::milliseconds>(now - _player->GetDeadTime()).count() >= PLAYER_BLACKOUT_TIME) { _player->SetIsRendering(true); } } if (std::chrono::duration_cast<std::chrono::milliseconds>(now - _player->GetDeadTime()).count() >= PLAYER_GHOST_TIME) { _player->SetIsGhost(false); } } if (_player->GetLives() <= 0 && !_gameOver) { _gameOver = true; _gameOverTimepoint = now; _player->SetIsGhost(true); _player->Stop(); } if (_player->GetIsRendering() && !_gameOver) { RefreshObjectCoord(_player); _player->Refresh(_controls->GetAngle(), _controls->GetAcceleration()); if (_controls->GetShoot()) { if (std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastShotTimepoint).count() >= NEXT_SHOT_TIME) { _shots.push_back(_player->GenerateShot()); _lastShotTimepoint = now; } } } for (auto it = _shots.begin(); it != _shots.end();) { RefreshObjectCoord(*it); (*it)->Refresh(_timeMultiplier); if ((*it)->GetDistance() >= std::min(_halfHeight, _halfWidth) * 2 - 1.2f) { delete(*it); it = _shots.erase(it); } else { ++it; } } for (AsteroidFamily *item : _asteroids) { if (item->GetLarge()->GetIsRendering()) { _isAsteroidsEmpty = false; RefreshObjectCoord(item->GetLarge()); item->GetLarge()->Refresh(); if (!_player->GetIsGhost()) { if (isCollision(_player, item->GetLarge())) { item->DestroyLarge(); _score += SCORE_LARGE; ProcessCollision(_player, item->GetLarge()); } } if (item->GetLarge()->GetIsRendering()) for (auto it = _shots.begin(); it != _shots.end();) { if (isCollision(*it, item->GetLarge())) { delete(*it); it = _shots.erase(it); item->DestroyLarge(); _booms.push_back(new Boom(item->GetLarge()->GetCoord().x, item->GetLarge()->GetCoord().y)); _score += SCORE_LARGE; } else { ++it; } } } else { if (item->GetFirstSmall()->GetIsRendering()) { _isAsteroidsEmpty = false; RefreshObjectCoord(item->GetFirstSmall()); item->GetFirstSmall()->Refresh(); if (!_player->GetIsGhost()) { if (isCollision(_player, item->GetFirstSmall())) { item->GetFirstSmall()->SetIsRendering(false); _score += SCORE_SMALL; ProcessCollision(_player, item->GetFirstSmall()); } } } if (item->GetSecondSmall()->GetIsRendering()) { _isAsteroidsEmpty = false; RefreshObjectCoord(item->GetSecondSmall()); item->GetSecondSmall()->Refresh(); if (!_player->GetIsGhost()) { if (isCollision(_player, item->GetSecondSmall())) { item->GetSecondSmall()->SetIsRendering(false); _score += SCORE_SMALL; ProcessCollision(_player, item->GetSecondSmall()); } } } for (auto it = _shots.begin(); it != _shots.end();) { bool isFirstCollision = false, isSecondCollision = false; if (item->GetFirstSmall()->GetIsRendering()) isFirstCollision = isCollision(*it, item->GetFirstSmall()); if (item->GetSecondSmall()->GetIsRendering()) isSecondCollision = isCollision(*it, item->GetSecondSmall()); if (isFirstCollision || isSecondCollision) { delete(*it); it = _shots.erase(it); if (isFirstCollision) { item->GetFirstSmall()->SetIsRendering(false); _booms.push_back(new Boom(item->GetFirstSmall()->GetCoord().x, item->GetFirstSmall()->GetCoord().y)); _score += SCORE_SMALL; } if (isSecondCollision) { item->GetSecondSmall()->SetIsRendering(false); _booms.push_back(new Boom(item->GetSecondSmall()->GetCoord().x, item->GetSecondSmall()->GetCoord().y)); _score += SCORE_SMALL; } } else { ++it; } } } } for (auto it = _booms.begin(); it != _booms.end();) { (*it)->Refresh(_timeMultiplier); if ((*it)->GetDuration() >= BOOM_MAX_DURATION) { delete(*it); it = _booms.erase(it); } else { ++it; } } } void Game::RefreshObjectCoord(Object *object) { object->SetCoord(object->GetCoord().x + object->GetVelocity().x * _timeMultiplier, object->GetCoord().y + object->GetVelocity().y * _timeMultiplier); if (object->GetCoord().x <= -_halfWidth) object->SetCoord(object->GetCoord().x + _halfWidth * 2, object->GetCoord().y); else if (object->GetCoord().x >= _halfWidth) object->SetCoord(object->GetCoord().x - _halfWidth * 2, object->GetCoord().y); if (object->GetCoord().y <= -_halfHeight) object->SetCoord(object->GetCoord().x, object->GetCoord().y + _halfHeight * 2); else if (object->GetCoord().y >= _halfHeight) object->SetCoord(object->GetCoord().x, object->GetCoord().y - _halfHeight * 2); } void Game::Render() { Refresh(); _render->Clear(); _render->RenderControls(_controls); _render->SetColor(OBJECTS_COLOR); if (_player->GetIsRendering() && !_gameOver) { if (_player->GetIsGhost()) _render->SetColor(PLAYER_GHOST_COLOR); _render->RenderPlayer(_player); } if (_gameOver) _render->SetColor(OBJECTS_GAMEOVER_COLOR); else _render->SetColor(OBJECTS_COLOR); for (AsteroidFamily *item : _asteroids) { if (item->GetLarge()->GetIsRendering()) { _render->RenderAsteroid(item->GetLarge()); } else { if (item->GetFirstSmall()->GetIsRendering()) { _render->RenderAsteroid(item->GetFirstSmall()); } if (item->GetSecondSmall()->GetIsRendering()) { _render->RenderAsteroid(item->GetSecondSmall()); } } } for (Shot *item : _shots) { _render->RenderShot(item); } _render->SetColor(BOOM_COLOR); for (Boom *item : _booms) { _render->RenderBoom(item); } _render->SetColor(TEXT_COLOR); if (!_gameOver) { _render->RenderScoreAndLives(_score, _player->GetLives()); } else { _render->RenderGameOver(_score); } } bool Game::isCollision(Player *player, Asteroid *asteroid) { const std::vector<Point> &playerPoints = *(player->GetPoints()); const std::vector<Point> &asteroidPoints = *(asteroid->GetPoints()); if (TestAABB(player, asteroid)) { for (unsigned i = 0; i < playerPoints.size(); i++) { for (unsigned j = 0; j < asteroidPoints.size(); j++) { if (Logic::IsLinesCross(playerPoints[i], playerPoints[(i + 1 == playerPoints.size()) ? 0 : i + 1], asteroidPoints[j], asteroidPoints[(j + 1 == asteroidPoints.size()) ? 0 : j + 1])) { return true; } } } if (Logic::IsInside(asteroidPoints, playerPoints[0])) { return true; } } return false; } bool Game::isCollision(Shot *shot, Asteroid *asteroid) { if (Logic::IsInside(*(asteroid->GetPoints()), shot->GetCoord())) return true; else return false; } bool Game::TestAABB(Player *player, Asteroid *asteroid) { return (player->GetSizes()[0] < asteroid->GetSizes()[1] && player->GetSizes()[1] > asteroid->GetSizes()[0] && player->GetSizes()[2] < asteroid->GetSizes()[3] && player->GetSizes()[3] > asteroid->GetSizes()[2]); } void Game::ProcessCollision(Player *player, Asteroid *asteroid) { _booms.push_back(new Boom(asteroid->GetCoord().x, asteroid->GetCoord().y)); player->SetIsRendering(false); player->SetIsGhost(true); player->SetLives(player->GetLives() - 1); player->SetCoord(0.0f, 0.0f); player->SetDeadTime(std::chrono::high_resolution_clock::now()); } void Game::Resize(float width, float height) { _aspectRatio = (float)width / (float)height; _halfWidth = _aspectRatio * _halfHeight; _width = width; _height = height; _render->Resize(); } Controls *Game::GetControls() { return _controls; } bool Game::GetIsPaused() { return _isPaused; } void Game::SetIsPaused(bool isPaused) { _isPaused = isPaused; } 


Game.cpp
 #include <Game.h> #include <Controls.h> #include <tuple> Game::Game() { isLevelRunning = true; ResetLogic(); RequestRestart(); Renderer::InitInternals(); Controls::Init(); Score::Init(); } Game& Game::Get() { static Game instance; return instance; } void Game::Restart() { objects.clear(); Score::OnRestart(); ResetLogic(); GameObject::Create<Ship>(); SpawnAsteroids(Constant::asteroidTargetCount); } //  ,       (false -  ) bool Game::IsLevelRunning(float dt) { if(wantRestart) { if(restartTimer < 0.0) { if(isLevelRunning) { Restart(); wantRestart = false; } } else { restartTimer -= dt; } } return isLevelRunning; } void Game::Update() { float deltaTime = timer.Tick(); if(IsLevelRunning(deltaTime)) { for(auto& go : objects) { go->Update(deltaTime); } DetectCollisions(deltaTime); DestroyRequestedObjects(); //     } Renderer::Draw(); } void Game::OnGLInit() { Renderer::InitGLContext(); } void Game::OnResolutionChange(int w, int h) { Renderer::OnResolutionChange(w, h); Controls::Resize(); Score::Resize(); } GameObject& Game::AddGameObject(std::unique_ptr<GameObject> obj) { objects.push_back(std::move(obj)); return *objects.back().get(); } void Game::DestroyRequestedObjects() { for(auto i = objects.begin(); i != objects.end();) { if((*i)->isDestructionRequested()) { i = objects.erase(i); } else { ++i; } } } void Game::DetectCollisions(float dt) { for(auto a = objects.begin(); a != objects.end(); ++a) { for(auto b = std::next(a); b != objects.end(); ++b) { //    GameObject& ra = **a; GameObject& rb = **b; if(CollisionMask(ra, rb)) { //    if(DetectCollision(ra, rb, dt)) { //  if(RefineCollision(ra, rb, dt)) { //  ra.OnCollision(rb); rb.OnCollision(ra); } } } } } } bool Game::CollisionMask(const GameObject& a, const GameObject& b) { //      ,       (return false) return !(a.isDestructionRequested() || b.isDestructionRequested()) && //          ,   false (a.CollisionMask(b.getStaticType()) || b.CollisionMask(a.getStaticType())); } //    ,    //      ,       dt bool Game::DetectCollision(const GameObject& a, const GameObject& b, float dt) { if(Constant::continuousCollisions) { return (a.getPosition() - b.getPosition()).getLength() < a.getRadius() + b.getRadius() + (a.getVelocity().getLength() + b.getVelocity().getLength()) * dt; } else { return (a.getPosition() - b.getPosition()).getLength() < a.getRadius() + b.getRadius(); } } bool Game::RefineCollision(const GameObject& a, const GameObject& b, float dt) { if(Constant::refineCollisions) { const Model& am = a.getModel(); const std::vector<GLfloat>& av = am.getTransformed(); const std::vector<GLubyte>& ai = am.getIndices(); const Vec2 aBackVel = a.getVelocity() * -1; const Model& bm = b.getModel(); const std::vector<GLfloat>& bv = bm.getTransformed(); const std::vector<GLubyte>& bi = bm.getIndices(); const Vec2 bBackVel = b.getVelocity() * -1; //    ,      for(int i = 0; i < ai.size(); i += 2) { Vec2 a0(i, av, ai); Vec2 at = Vec2(i + 1, av, ai) - a0; for(int j = 0; j < bi.size(); j += 2) { Vec2 b0(j, bv, bi); Vec2 bt = Vec2(j + 1, bv, bi) - b0; if(Constant::continuousCollisions ? // ,       t0, //     t0+dt,      - MovingSegmentCollision(a0, at, aBackVel, b0, bt, bBackVel, dt) : SegmentCollision(a0, at, b0, bt)) return true; } } return false; } else { return true; } } bool Game::SegmentCollision(Vec2 p, Vec2 r, Vec2 q, Vec2 s) { //http://stackoverflow.com/a/565282/2502024 float det = Vec2::CrossProd2D(r, s); if(fabs(det) > Constant::smallNumber) { Vec2 diff = q - p; float f = Vec2::CrossProd2D(diff, s / det); float g = Vec2::CrossProd2D(diff, r / det); return f >= 0 && f <= 1 && g >= 0 && g <= 1; } return false; } bool Game::MovingSegmentCollision(Vec2 p, Vec2 r, Vec2 vp, Vec2 q, Vec2 s, Vec2 vq, float dt) { float det = Vec2::CrossProd2D(r, s); if(fabs(det) > Constant::smallNumber) { const Vec2 v = vq - vp; const Vec2 diff = q - p; //    : //q = q0 + v*t, t in [0, dt] //(vx * sy - vy * sx) * t + ((qx - px) * sy - (qy - py) * sx) //  f  g  SegmentCollision    t. //    t  [0, dt],    t, // f  g    [0, 1]. ..  3    t auto getInequation = [=](Vec2 dir)->std::tuple<float, float> { //0 <= a*t + cp <= 1 float cp = Vec2::CrossProd2D(diff, dir / det); float a = Vec2::CrossProd2D(v, dir / det); float left = -cp, right = 1 - cp; if(fabs(a) < Constant::smallNumber) { if(cp >= 0 && cp <= 1) { left = 0; right = dt; } else { left = dt + 1; right = -1; } } else { left /= a; right /= a; if(a < 0) std::swap(left, right); } return std::make_tuple(left, right); }; float ls, rs, lr, rr; //ls <= t <= rs std::tie(ls, rs) = getInequation(s); //lr <= t <= rr std::tie(lr, rr) = getInequation(r); //  0 <= t <= dt float mx = std::max(0.f, std::max(ls, lr)); float mn = std::min(dt, std::min(rs, rr)); return mx <= mn; } return false; } void Game::RequestRestart(float t) { wantRestart = true; restartTimer = t; } void Game::Pause() { isLevelRunning = false; Controls::onPause(); } void Game::Resume() { isLevelRunning = true; Controls::onResume(); timer.Tick(); } void Game::SetPlayerPos(const Ship& player) { playerPos = player.getPosition(); } Vec2 Game::GetPlayerPos() { return playerPos; } void Game::AddPoints(int pointsToAdd) { Score::AddPoints(pointsToAdd); } void Game::DecAsteroidCount(const Asteroid& a) { asteroidCount--; if(asteroidCount == Constant::asteroidUfoCount * 2 && !isUfoPresent) { GameObject::Create<UFO>(GetUfoSpawn()); } if(asteroidCount <= Constant::asteroidRespawnCount * 2) { SpawnAsteroids(Constant::asteroidTargetCount - Constant::asteroidRespawnCount); } } //  2, .. asteroidCount      void Game::IncAsteroidCount(const Asteroid& a) { asteroidCount += 2; } void Game::ResetLogic() { asteroidCount = 0; isUfoPresent = false; } void Game::SpawnAsteroids(int n) { for(int i = 0; i < n; ++i) { GameObject::Create<Asteroid>(GetSpawnPosition()); } } //  ,      (    ) Transform Game::GetSpawnPosition() { std::uniform_real_distribution<float> zone(-Constant::asteroidSpawnZone, Constant::asteroidSpawnZone); Vec2 pos(Constant::worldRatio + 0.2, 1.2); if(fabs(playerPos.x) > Constant::asteroidSpawnZone && fabs(playerPos.y) < Constant::asteroidSpawnZone) { pos.y = zone(Random::generator); } else if(fabs(playerPos.x) < Constant::asteroidSpawnZone && fabs(playerPos.y) > Constant::asteroidSpawnZone) { pos.x = zone(Random::generator); } else { if(Random::flipCoin()) { pos.x = zone(Random::generator); } else { pos.y = zone(Random::generator); } } return Transform(pos); } Transform Game::GetUfoSpawn() { std::uniform_real_distribution<float> zone(-Constant::ufoZone, Constant::ufoZone); return Transform((Constant::worldRatio + 0.12) * (playerPos.x > 0 ? -1 : 1), zone(Random::generator)); } void Game::OnUfoCreated(const UFO& u) { isUfoPresent = true; } void Game::OnUfoDestroyed(const UFO& u) { isUfoPresent = false; } 


model_handler.cpp
 #include "model_handler.h" #include <time.h> #include <math.h> #include <cstdlib> using namespace model; namespace { const double tickTime = 40.00; const unsigned int asteroidNumber = 8; const float projectileSpeed = 10.0; const float smallAsteroidRadiusK = 1.5; const float minLargeAsteroidRadius = 35.0; const float maxLargeAsteroidRadius = minLargeAsteroidRadius * smallAsteroidRadiusK - 1.0; const float minAsteroidSpeed = 1.5; const float maxAsteroidSpeed = 6.5; const float explosionK = 50000.0; } ModelHandler::ModelHandler(float worldWidth, float worldHeight): _isGameOver(false), _worldWidth(worldWidth), _worldHeight(worldHeight), _tickTime(0), _ship(ShipPtr(new Ship(Point(worldWidth / 2.0, worldHeight / 2.0)))) { srand(time(0)); } void ModelHandler::newGame() { _asteroids.clear(); _projectiles.clear(); _ship.reset(new Ship(Point(_worldWidth / 2.0, _worldHeight / 2.0))); _isGameOver = false; } bool ModelHandler::isGameOver() const { return _isGameOver; } void ModelHandler::update(double deltaTime) { if (this->isGameOver()) return; _tickTime += deltaTime; while (_tickTime > tickTime) { if (_asteroids.size() < ::asteroidNumber) { this->addAsteroid(); } for (ObjectPtr& obj: this->allObjects()) { obj->move(); } this->checkObjects(&_asteroids); this->checkObjects(&_projectiles); if (!this->withinBoundaries(_ship)) { this->removeShip(); } _tickTime -= tickTime; } } ShipPtr ModelHandler::ship() const { return _ship; } void ModelHandler::removeShip() { _isGameOver = true; } void ModelHandler::removeAsteroid(const AsteroidPtr& asteroid) { if (asteroid->collisionRadius() >= ::minLargeAsteroidRadius) { const model::Vector v1(asteroid->velocity() + model::Vector(-asteroid->velocity().y, asteroid->velocity().x)); const model::Vector v2(asteroid->velocity() + model::Vector(asteroid->velocity().y, -asteroid->velocity().x)); this->addAsteroid(asteroid, v1); this->addAsteroid(asteroid, v2); } _asteroids.remove(asteroid); } void ModelHandler::addAsteroid(const AsteroidPtr& asteroid) { _asteroids.push_back(asteroid); } std::list<AsteroidPtr> ModelHandler::asteroids() const { return _asteroids; } std::list<ProjectilePtr> ModelHandler::projectiles() const { return _projectiles; } void ModelHandler::removeProjectile(const ProjectilePtr& projectile) { _projectiles.remove(projectile); } void ModelHandler::addProjectile() { Vector projectileSpeed = _ship->direction() * ::projectileSpeed + ship()->velocity(); _projectiles.push_back(ProjectilePtr(new Projectile(_ship->point(), projectileSpeed))); } void ModelHandler::processHit(const ProjectilePtr& projectile, const AsteroidPtr& asteroid) { if (asteroid->collisionRadius() >= ::minLargeAsteroidRadius) { const Vector ox = asteroid->velocity().normaVector(); Vector explosionVector(ox.y, -ox.x); if (ox * asteroid->velocity() < 0) { explosionVector = explosionVector * (-1.0); } const Vector v1(asteroid->velocity() + model::Vector(-asteroid->velocity().y, asteroid->velocity().x) + explosionVector * (-::explosionK / asteroid->mass())); const Vector v2(asteroid->velocity() + model::Vector(asteroid->velocity().y, -asteroid->velocity().x) + explosionVector * (::explosionK / asteroid->mass())); this->addAsteroid(asteroid, v1); this->addAsteroid(asteroid, v2); } _projectiles.remove(projectile); _asteroids.remove(asteroid); } std::list<ObjectPtr> ModelHandler::allObjects() const { std::list<ObjectPtr> objects; objects.insert(objects.end(), _asteroids.begin(), _asteroids.end()); objects.insert(objects.end(), _projectiles.begin(), _projectiles.end()); objects.push_back(_ship); return objects; } void ModelHandler::addAsteroid() { const float r = common::rangeRand(::minLargeAsteroidRadius, ::maxLargeAsteroidRadius); Point point; bool isCorrect = false; while (!isCorrect) { point = randPoint(r); isCorrect = true; for (const AsteroidPtr& asteroid : _asteroids) { const float d = model::distance(asteroid->point(), point); if (d < (asteroid->collisionRadius() + r)) { isCorrect = false; break; } } } const float distToCenter = ::distance(point.x, point.y, _worldWidth / 2.0, _worldHeight / 2.0); const float vx = (_worldWidth / 2.0 - point.x) / distToCenter * common::rangeRand(::minAsteroidSpeed, ::maxAsteroidSpeed); const float vy = (_worldHeight / 2.0 - point.y) / distToCenter * common::rangeRand(::minAsteroidSpeed, ::maxAsteroidSpeed); _asteroids.push_back(AsteroidPtr(new Asteroid(point, r, Vector(vx, vy)))); } void ModelHandler::addAsteroid(const AsteroidPtr& oldAsteroid, const Vector& newVelocity) { Point point = oldAsteroid->point(); point.move(newVelocity.normaVector() * oldAsteroid->collisionRadius()); _asteroids.push_back(AsteroidPtr(new Asteroid(point, oldAsteroid->collisionRadius() / ::smallAsteroidRadiusK, newVelocity))); } Point ModelHandler::randPoint(float r) const { float x = 0; float y = 0; switch (rand() % 4) { case 0: x = common::rangeRand(0 - r, _worldWidth + r); y = 0 - r; break; case 1: x = common::rangeRand(0 - r, _worldWidth + r); y = _worldHeight - r; break; case 2: x = 0 - r; y = common::rangeRand(0 - r, _worldHeight + r); break; case 3: x = _worldWidth + r; y = common::rangeRand(0 - r, _worldHeight + r); break; default: break; } return Point(x, y); } bool ModelHandler::withinBoundaries(const ObjectPtr& obj) const { return (obj->x() > (0 - _worldWidth * 0.1) && obj->x() < (_worldWidth * 1.1) && obj->y() > (0 - _worldHeight * 0.1) && obj->y() < (_worldHeight * 1.1)); } template<class T> void ModelHandler::checkObjects(std::list<T>* objects) { typename std::list<T>::iterator it = objects->begin(); while (it != objects->end()) { if (!this->withinBoundaries(*it)) { it = objects->erase(it++); } else { ++it; } } } 



, . . , ? , , .

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


All Articles