Touch Controller Interface
Genre : 2D Top-Down Shooter
Technology : C++, Android NDK, Tegra Android Development Pack (TADP), Nvidia Nsight
Thesis objective : Experiment how well people can handle each input scheme. Is it possible to measure this? Below is my thinking process about how to conclude this topic.
How to measure : I developed a data-driven control scheme system, which allows me to change movement component and shooting component for each control scheme. I developed a shooting game, built levels, let people play with them and collect the players’ data. After testers are finished with the game, they have to fill the post-test survey.
Controller Description :
Movement component :
- Joystick fixed at bottom-left corner
- Joystick fixed at center
- Joystick is Drag-able
- Swipe
Shooting component :
- Two buttons
- One button
- Second joystick
- Touch to shoot
Game Mechanics Description :
The ultimate goal is to make people moving around with great precision as much as possible. Here is the game mechanics that encourage testers to do that.
- Game objects
- Coins – collectible objects, disappear when a player touches it.
- Targets – disappear when a player’s bullet touches it
- Walls – used for collecting number of wall hits
- Cannons – shoot bullets, used for collecting number of bullet hits
- Level design principle
- I split the levels into three categories
- First, to test the directional movement of the scheme, I built the maze level and evaluate the player by the time he takes to finish the level. The level consists of long, narrow, zigzag and U-turn hallway.
- Second, to test the precision of the scheme, I built a Targets level which have many targets in various positions. Players have to move and turn to shoot down all targets
- Third, to test directional movement in tight space, I built levels with cannons, which shoot bullets every few seconds. Players have to move through field of bullets to test their capability to move between dynamic obstacles (bullets)
- The level can be easily built in 10 minutes, thanks to my 2D Editor. The levels are built on Windows and can be used on Android without any problems.
- I split the levels into three categories
Lesson Learned :
- Working on new technology is hard. I used Google as much as I can and found almost nothing. Mostly I have to experiment them by myself.
- Working on new technology with special condition is exceptionally hard. I started out the project with Intel tablet, which required a special driver and setup. With some limitations (obviously, not technical limitation), I change the tablet to Nexus7 later on and found that a lot of work I have done and not work before magically work just because I change the tablet. On top of that, most of the solution I found on Google cannot be applied to my problem because of this special condition on that tablet.
- I didn’t plan to have the game run on Windows at the first place. Thanks to NDK lacking of debugging feature (yes, I can use print log to debug but imagine how annoying+time consuming it is), so I come up with a better way – working on Windows and move all the game code directly to Android after I finished debugging. To make this happen, my engine must not have any platform-specific code, which I think it’s very good for learning experience and keeping my code clean at the same time.
- Sadly, the result is I have to finish everything on Android first, and then try to port it to Windows after that. The reason is, Android has very weird way to handle things and has a lot more limitation compare to Windows. For example, I used to initiate the Shader and my actor objects in the engine constructor. I cannot do that in Android because my actors need a texture and the texture can be loaded after I already initiate the OGL context. In Android, I can only have OGL context after I have the surface (AKA android game screen), so I must let it run into the main loop for a few millisecond before I can used the OGL context data. With that limitation, it’s impossible to initiate anything that need OGL context in the constructor.
- There is also a lot of minor conditions that get in my way to port from Windows to Android (and vice versa). Mostly because Android code and example heavily rely on its own platform-specific code.
- Working on familiar IDE make things 100 times easier. I have to work on Eclipse (NDK) for a while and everything is slow – I can’t use the short keys and debugger, the intellisense is not working. I found Nsight for Visual Studio and I put my time to configure it. The payoff is very nice; I wouldn’t be able to figure out weird memory bug in Android with print log nor just my logic error very easy without debugger. In addition, my coding speed is a lot faster because I can use short keys like I normally did. It doesn’t mean I have to use debugger, but it’s a very nice feature to have *if* you work on something that you never did it before. You have no idea where it’s going to crash and why.
- It doesn’t matter I’m a programmer, a level designer or an artist. Responsibility and determination are the keys. If I have to get things done, it’s better to rely on myself. It’s also fun to explore new things (design levels or photoshopped arts) that I never did before.
Engine Code Sample
#ifndef __ENGINE_H #define __ENGINE_H #include "Renderer.h" #include "EGLInterface.h" #include "drawRect.h" #include "drawVertex.h" #include "SoundPlayer.h" #include "TextRenderer.h" #include "NvMultiInput.h" #include "PlayerPawn.h" #include "EnemyPawn.h" #include "TurretPawn.h" #include "Spawner.h" #include "MessageActor.h" #include "InputController.h" #include "BulletPatternGenerator.h" #include "TestBulletGeneratorPackage.h" #include "TestEnemyGeneratorPackage.h" #include "Level_Loader.h" // set max const beyond anything available soon. #define MAX_TOUCH_COUNT 20 namespace banknamespace { class Engine { public: Engine(); ~Engine(); bool isGameplayMode() { return mGameplayMode; } void updateFrame(bool interactible, long deltaTime); void init(std::vector<void*> args ); void setGameplayMode(bool paused); void advanceTime(long time) { mTimeVal = Time::GetAbsoluteTimeSeconds(); } bool isForcedRenderPending() { return mForceRender > 0; } void requestForceRender() { mForceRender = 4; } bool checkWindowResized(); bool renderFrame(bool allocateIfNeeded); void writeStatLog(); void loadNewLevel(); bool initUI(); bool resizeIfNeeded(); int loadBulletPackage( const char* path ); std::map<std::string, int> packageNameMap; void* mApp; EGLInterface* mEgl; bool mResizePending; bool mGameplayMode; int mForceRender; double mTimeVal; bool m_uiInitialized; NvMultiInput *startTouchList; // input multitouch int m_pointerCount; GLint m_touchTexture; drawRect *m_drawRect; ///// my variable // text on screen int txt_playerHP; int txt_playerXP; int txt_playerLV; int txt_playerControl; int txt_clockText; PlayerPawn reassignedPlayer; // controller texture unsigned int tex_ButtonA; unsigned int tex_ButtonB; unsigned int tex_baseController; unsigned int tex_stickController; unsigned int tex_panel; // feedback texture Actor SparkleActor; MessageActor LvUpActor; ///// ///// engine // renderer Renderer* renderer; TextRenderer* t_renderer; bool alreadyInitTextRenderer; double lastFrameTime; double currentTime; double aspectRatio; mat16f projectionMatrix; unsigned int textureId; std::vector< actorVertex > vertices; std::vector< unsigned int > indices; unsigned int VBO; unsigned int IBO; // sound SoundPlayer* m_soundPlayer; unsigned int bgm_sound; unsigned int playerShoot; unsigned int enemyShoot; unsigned int feederShoot; unsigned int turretShoot; ///// shooter game variable vec3f playerImpulse; Camera m_camera; Level_Loader level; std::vector<std::string> levelPath; std::vector<std::string> levelSoundPath; bool isLevelChange; int levelNum; int maxLevelNum; EnemyPawn feederPrototype; TurretPawn turretPrototype; Spawner spawnerPrototype; Actor healthPrototype; std::vector<initPackage> bulletPackageArr; std::vector< PlayerPawn > playerActor; std::vector< EnemyPawn > enemyActorArr; std::vector< Spawner > spawnerActorArr; std::vector< EnemyPawn > feederActorArr; std::vector< Actor > healthActorArr; std::vector< TurretPawn > turretActorArr; TestEnemyGeneratorPackage enPack; std::vector<BulletPatternGenerator> playerBulletGenerator; std::vector<BulletPatternGenerator> enemyBulletGenerator; unsigned int playerIndex; ///// }; } #endif // __ENGINE_H
#include "StdAfx.h" #include "engine.h" #include "Define.h" static const float color[4] = {1.0f, 1.0f, 1.0f, 1.0f}; namespace banknamespace { Engine::Engine() { mResizePending = false; mGameplayMode = true; mForceRender = 4; mTimeVal = 0.0; m_uiInitialized = false; m_pointerCount = 0; lastFrameTime = 0; alreadyInitTextRenderer = false; isLevelChange = false; levelNum = 0; txt_playerHP = 0; txt_playerXP = 0; txt_playerLV = 0; txt_playerControl = 0; txt_clockText = 0; } void Engine::init( std::vector<void*> args ) { ///// args[0] = android_app* mApp ///// args[1] = Renderer* renderer ///// args[2] = NvEGLUtil* mEgl ///// args[3] = NvMultiInput* startTouchList ///// args[4] = TextRenderer* t_renderer; ///// args[5] = levelNum; mApp = args[0]; renderer = reinterpret_cast<Renderer*>( args[1] ); mEgl = reinterpret_cast<EGLInterface*>( args[2] ); startTouchList = reinterpret_cast<NvMultiInput*>( args[3] ); t_renderer = reinterpret_cast<TextRenderer*>( args[4] ); int tmpNum = *reinterpret_cast<int*>( args[5] ); levelPath.push_back("Resources/SaveFile/tutorial01.edt"); levelPath.push_back("Resources/SaveFile/bank_LevelMaze.edt"); levelPath.push_back("Resources/SaveFile/bank_LevelEnemies1.edt"); levelPath.push_back("Resources/SaveFile/bank_LevelEnemies2.edt"); levelPath.push_back("Resources/SaveFile/bank_LevelEnemies3.edt"); levelPath.push_back("Resources/SaveFile/bank_LevelEnemies4.edt"); levelPath.push_back("Resources/SaveFile/bank_LevelEnemies5.edt"); maxLevelNum = levelPath.size() - 1; if (tmpNum > maxLevelNum) tmpNum = maxLevelNum; levelNum = tmpNum; InputController::isControlChanged = true; } Engine::~Engine() { m_soundPlayer->play(bgm_sound, false); delete m_drawRect; } int Engine::loadBulletPackage( const char* path ) { // load from file int length = 0; char* raw_data = loadNDKfile(renderer->getAndroidApp() , path, length); std::vector<bulletStateInit> tmpInit; std::vector<bulletStateMovement> tmpMovement; // found file fileStruct tmpStruct; memcpy( &tmpStruct, raw_data, sizeof(fileStruct) ); // make a room for data tmpInit.resize(tmpStruct.initSize); tmpMovement.resize(tmpStruct.moveSize); memcpy( tmpInit.data(), raw_data + sizeof(fileStruct), sizeof(bulletStateInit) * tmpStruct.initSize ); memcpy( tmpMovement.data(), raw_data + sizeof(fileStruct) + sizeof(bulletStateInit) * tmpStruct.initSize, sizeof(bulletStateMovement) * tmpStruct.moveSize ); // import data to tmp package initPackage tmp; tmp.name = path; tmp.initList = tmpInit; tmp.movementList = tmpMovement; tmp.collisionRadius = 8.0f; bulletPackageArr.push_back( tmp ); free(raw_data ); return bulletPackageArr.size()-1; } void Engine::writeStatLog() { // log stat before load new level std::string tmpStr; // dieCount, takeHit, XPgained, tmpStr.append("\nlv="); tmpStr.append( levelPath[levelNum] ); switch (InputController::currentControlType) { case CONTROLTYPE_ORIGINAL : tmpStr.append("\tOriginal"); break; case CONTROLTYPE_MOVINGCENTER : tmpStr.append("\tMovingCenter"); break; case CONTROLTYPE_PLAYERCENTER : tmpStr.append("\tPlayerCenter"); break; case CONTROLTYPE_SWIPE : tmpStr.append("\tSwipe"); break; default : break; } int mins = mTimeVal / 60; float secs = (float)mTimeVal - mins*60; char str[32]; sprintf(str, "\t%03d:%05.2f", mins, secs); tmpStr.append( str ); tmpStr.append("\tHP="); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].HP) ); tmpStr.append("\tXP="); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].XP) ); tmpStr.append("/"); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].nextLV_XP) ); tmpStr.append("\tLV="); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].LV) ); tmpStr.append("\t"); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].dieCount) ); tmpStr.append("\t"); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].takeHit) ); tmpStr.append("\t"); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].XPgained) ); tmpStr.append("\t"); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].turretKilled) ); tmpStr.append("\t"); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].spawnerKilled) ); tmpStr.append("\t"); tmpStr.append( lexical_cast<std::string, int>(playerActor[0].otherKilled) ); writeFile("/sdcard/playerStat.txt", tmpStr.c_str()); } void Engine::loadNewLevel() { // log stat before load new level writeStatLog(); // load new level isLevelChange = true; levelNum++; } bool Engine::initUI() { if (m_uiInitialized) return true; // calc aspect ratio float w = mEgl->getWidth(); float h = mEgl->getHeight(); aspectRatio = w / h; InputController::width = mEgl->getWidth(); InputController::height = mEgl->getHeight(); float projection[16]; buildProjectionMatrix( projection, 45.0f, static_cast<float>(aspectRatio), 0.1f, 10000.0f ); projectionMatrix = mat16f( projection ); //////////////////////////////////////// ///// set static variable here //////////////////////////////////////// ///// init actor // debug texture Actor::hurtTextureId = renderer->loadTexture( "Sprite/explosion.png" ); Actor::shieldTextureId = renderer->loadTexture( "Sprite/energyBallBlue.png" ); Actor::setDebugTextureId( renderer->loadTexture( "Sprite/textureBoundary.png" ) ); Actor::setDebugCircleTextureId( renderer->loadTexture( "Sprite/circleBoundary.png" ) ); BulletPatternGenerator::setDebugTextureId( renderer->loadTexture( "Sprite/textureBoundary.png" ) ); BulletPatternGenerator::setDebugCircleTextureId( renderer->loadTexture( "Sprite/circleBoundary.png" ) ); Spawner::enemyActorArr = &enemyActorArr; Spawner::enemyBulletGenerator = &enemyBulletGenerator; Actor::m_renderer = renderer; Actor::m_soundplayer = m_soundPlayer; // stage level.init( renderer, levelPath[levelNum].c_str() ); Actor::setLevel( &level ); //////////////////////////////////////// ///// end of static variable //////////////////////////////////////// enPack.init(renderer, &packageNameMap); ///// // load all bullet packages Pawn::bulletPackageArr = &bulletPackageArr; // player bullet got first slot std::string name = "bulletInitFiles/bPlayer01.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bPlayer02.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bPlayer03.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bPlayer04.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bPlayer05.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); // enemies bullet name = "bulletInitFiles/bSingle.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bTriple.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bSixCircle.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bFeeder.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bDualShot.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); name = "bulletInitFiles/bSpin.bInit"; packageNameMap[name] = loadBulletPackage(name.c_str()); ///// //// player playerIndex = playerActor.size(); { PlayerPawn tmpActor; tmpActor.init(*renderer, &playerBulletGenerator, &LvUpActor); tmpActor.setBulletTexture( renderer->loadTexture( "Sprite/playerBullet.png" ), playerShoot ); tmpActor.setTexture( renderer->loadTexture( "Sprite/playerSprite.png" ) ); tmpActor.fireInterval = 0.3; playerActor.push_back( tmpActor ); } // text on screen (+player data) init if (!alreadyInitTextRenderer) { t_renderer->init(); txt_playerHP = t_renderer->allocTextBlock(); txt_playerXP = t_renderer->allocTextBlock(); txt_playerLV = t_renderer->allocTextBlock(); txt_playerControl = t_renderer->allocTextBlock(); txt_clockText = t_renderer->allocTextBlock(); alreadyInitTextRenderer = true; } else { // persistent player data playerActor[0].gainLV(reassignedPlayer.LV-1); playerActor[0].HP = reassignedPlayer.HP; playerActor[0].XP = reassignedPlayer.XP; } #ifdef _GLWINDOWS t_renderer->setTextProperties(txt_playerHP, 25, FONTCOLOR_RED); t_renderer->setTextProperties(txt_playerXP, 25, FONTCOLOR_RED); t_renderer->setTextProperties(txt_playerLV, 25, FONTCOLOR_RED); t_renderer->setTextProperties(txt_playerControl, 25, FONTCOLOR_RED); t_renderer->setTextProperties(txt_clockText, 15, FONTCOLOR_WHITE); { int w = mEgl->getWidth(); int h = mEgl->getHeight(); int height = (w > h) ? (h / 16) : (w / 16); drawRect::setScreenResolution(w, h); drawVertex::setScreenResolution(w, h); t_renderer->setScreenResolution(w, h); t_renderer->setTextPosition(txt_playerControl, 40, 70); t_renderer->setTextPosition(txt_playerHP, 40, 70+(height)); t_renderer->setTextPosition(txt_playerXP, 40, 70+(2*height)); t_renderer->setTextPosition(txt_playerLV, 40, 70+(3*height)); t_renderer->setTextPosition(txt_clockText, w-100, 70); } #elif defined(_NVANDROID) t_renderer->setTextProperties(txt_playerHP, 40, FONTCOLOR_RED); t_renderer->setTextProperties(txt_playerXP, 40, FONTCOLOR_RED); t_renderer->setTextProperties(txt_playerLV, 40, FONTCOLOR_RED); t_renderer->setTextProperties(txt_playerControl, 40, FONTCOLOR_RED); t_renderer->setTextProperties(txt_clockText, 20, FONTCOLOR_WHITE); { int w = mEgl->getWidth(); int h = mEgl->getHeight(); int height = (w > h) ? (h / 16) : (w / 16); drawRect::setScreenResolution(w, h); drawVertex::setScreenResolution(w, h); t_renderer->setScreenResolution(w, h); t_renderer->setTextPosition(txt_playerControl, 40, 40); t_renderer->setTextPosition(txt_playerHP, 40, 40+(height)); t_renderer->setTextPosition(txt_playerXP, 40, 40+(2*height)); t_renderer->setTextPosition(txt_playerLV, 40, 40+(3*height)); t_renderer->setTextPosition(txt_clockText, w-80, 10); } #endif ///// feedback actor SparkleActor.init(*renderer); SparkleActor.setTexture( renderer->loadTexture( "Sprite/sparkles.png" ) ); SparkleActor.setDrawSize(10.0f); LvUpActor.init(*renderer); LvUpActor.setDrawSize(20.0f,20.0f); LvUpActor.setTexture( renderer->loadTexture( "Sprite/LevelUp.png" ) ); ///// EnemyPawn::playerPawn = &playerActor[0]; Spawner::playerPawn = &playerActor[0]; vec3f tmpPos = vec3f(level.enterGrid[0].getPosition()); playerActor[0].setPosition(tmpPos); playerActor[0].update(0.01); // set prototype here feederPrototype.init(*renderer, &enemyBulletGenerator); feederPrototype.HP = 150.0f; feederPrototype.currentEnemyType = eENEMY_FEEDER; feederPrototype.setBulletTexture( renderer->loadTexture("Sprite/feederBullet.png"), feederShoot ); feederPrototype.setTexture( renderer->loadTexture("Sprite/feederSprite.png") ); feederPrototype.maxLifeTime = 20.0; feederPrototype.fireInterval = 1.0f; feederPrototype.loadBulletPackage( packageNameMap["bulletInitFiles/bFeeder.bInit"] ); turretPrototype.init(*renderer, &enemyBulletGenerator); turretPrototype.HP = 250.0f; turretPrototype.currentEnemyType = eENEMY_TURRET; turretPrototype.setBulletTexture( renderer->loadTexture("Sprite/TurretBullet.png"), turretShoot ); turretPrototype.setTexture( renderer->loadTexture("Sprite/turretGun.png"), renderer->loadTexture("Sprite/turretBase.png") ); turretPrototype.maxLifeTime = 20.0; turretPrototype.loadBulletPackage( packageNameMap["bulletInitFiles/bPlayer01.bInit"] ); // set spawner prototype initSpawnerPackage spawnPack; enPack.getSpawnerPackage0(spawnPack, enemyShoot); spawnerPrototype.setDrawSize( level.gridSize ); spawnerPrototype.setCollisionSize( level.gridSize ); spawnerPrototype.setSpawnerType(spawnPack); healthPrototype.init(*renderer); healthPrototype.setTexture( renderer->loadTexture("Sprite/healthPickup.png") ); for (int i = 0; i < level.generatorGrid.size(); ++i) { Spawner tmpActor = spawnerPrototype; tmpActor.setPosition( vec3f(level.generatorGrid[i].getPosition()) ); spawnerActorArr.push_back( tmpActor ); } for (int i = 0; i < level.feederGrid.size(); ++i) { EnemyPawn tmpActor = feederPrototype; tmpActor.setPosition(level.feederGrid[i].getPosition()); feederActorArr.push_back( tmpActor ); } for (int i = 0; i < level.turretGrid.size(); ++i) { TurretPawn tmpActor = turretPrototype; tmpActor.setPosition(level.turretGrid[i].getPosition()); turretActorArr.push_back( tmpActor ); } for (int i = 0; i < level.healthGrid.size(); ++i) { Actor tmpActor = healthPrototype; tmpActor.setPosition(level.healthGrid[i].getPosition()); tmpActor.update(0.01); healthActorArr.push_back( tmpActor ); } m_camera.setPosition( playerActor[playerIndex].getPosition() + vec3f(0.0f, 300.0f, 0.0f) ); m_camera.setRotation(vec3f(-90.0f, 180.0f, 0.0f)); InputController::player = &playerActor[0]; tex_ButtonA = renderer->loadTexture( "ControlTexture/buttonA.png" ); tex_ButtonB = renderer->loadTexture( "ControlTexture/buttonB.png" ); tex_baseController = renderer->loadTexture( "ControlTexture/controllerBase.png" ); tex_stickController = renderer->loadTexture( "ControlTexture/controllerStick.png" ); tex_panel = renderer->loadTexture( "ControlTexture/panel.png" ); m_touchTexture = renderer->loadTexture( "Sprite/bullet3.png" ); m_drawRect = new drawRect(); m_uiInitialized = true; return true; } void Engine::setGameplayMode(bool running) { if (mGameplayMode != running) requestForceRender(); mGameplayMode = running; } bool Engine::checkWindowResized() { if (mEgl->checkWindowResized()) { mResizePending = true; requestForceRender(); return true; } return false; } bool Engine::resizeIfNeeded() { if (!mResizePending) return false; int w = mEgl->getWidth(); int h = mEgl->getHeight(); int height = (w > h) ? (h / 16) : (w / 16); drawRect::setScreenResolution(w, h); drawVertex::setScreenResolution(w, h); t_renderer->setScreenResolution(w, h); t_renderer->setTextPosition(txt_playerControl, 40, 40); t_renderer->setTextPosition(txt_playerHP, 40, 40+(height)); t_renderer->setTextPosition(txt_playerXP, 40, 40+(2*height)); t_renderer->setTextPosition(txt_playerLV, 40, 40+(3*height)); t_renderer->setTextPosition(txt_clockText, w-80, 10); mResizePending = false; return true; } bool Engine::renderFrame(bool allocateIfNeeded) { if (!mEgl->isReadyToRender(allocateIfNeeded)) return false; if (!initUI()) { #ifdef _GLWINDOWS return false; #elif defined(_NVANDROID) android_app* tmpApp = reinterpret_cast<android_app*>(mApp); ANativeActivity_finish(tmpApp->activity); return false; #endif } resizeIfNeeded(); const int w = mEgl->getWidth(); const int h = mEgl->getHeight(); // set up viewport glViewport((GLint)0, (GLint)0, (GLsizei)w, (GLsizei)h); // clear buffers as necessary glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // do some game rendering here if (mForceRender > 0) mForceRender--; InputController::updateFrame(); // smooth camera vec3f tmpPos = Lerp(m_camera.position(), playerActor[playerIndex].getPosition() + vec3f(0.0f, 300.0f, 0.0f), 0.2f); m_camera.setPosition( tmpPos ); m_camera.update(); m_camera.setViewTransformation(); mat16f vpMatrix = m_camera.camMatrixInv * projectionMatrix; currentTime = Time::GetAbsoluteTimeSeconds(); double deltaTime = currentTime - lastFrameTime; deltaTime = 0.003; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); level.draw(0, vpMatrix); // update player for (unsigned int i = 0; i < playerActor.size(); ++i) { playerActor[i].update( deltaTime ); if (!playerActor[i].getIsDead()) { playerActor[i].draw(0, vpMatrix); } } // update spawners for (unsigned int i = 0; i < spawnerActorArr.size(); ++i) { if (!spawnerActorArr[i].getIsDead()) { spawnerActorArr[i].update( deltaTime ); spawnerActorArr[i].draw(0, vpMatrix); } } // update turret for (unsigned int i = 0; i < turretActorArr.size(); ++i) { if (!turretActorArr[i].getIsDead()) { turretActorArr[i].update( deltaTime ); turretActorArr[i].draw(0, vpMatrix); } } // update health for (unsigned int i = 0; i < healthActorArr.size(); ++i) { if (!healthActorArr[i].getIsDead()) { // chk collision with player Actor* tmpActor = &healthActorArr[i]; vec3f tmpVec = tmpActor->getPosition(); if (!tmpActor->getIsDead()) if ( playerActor[0].chkCollision(tmpVec, tmpActor->getCollisionSize()) ) { // add HP playerActor[0].getHealthPickup(); tmpActor->setIsDead(true); } healthActorArr[i].draw(0, vpMatrix); } } // update feeder Tick_killDead(feederActorArr, deltaTime, 0, vpMatrix); // update enemies if (enemyActorArr.size() > 6) { int removeElements = enemyActorArr.size() - 6; enemyActorArr.erase(enemyActorArr.begin()); } Tick_killDead(enemyActorArr, deltaTime, 0, vpMatrix); // update player bullet TouhouTick_killDead(playerBulletGenerator, 0.01f); for (int k = 0; k < playerBulletGenerator.size(); ++k) { // draw playerBulletGenerator[k].draw(0, vpMatrix); } // update enemies bullet TouhouTick_killDead(enemyBulletGenerator, 0.01f); for (int k = 0; k < enemyBulletGenerator.size(); ++k) { // draw enemyBulletGenerator[k].draw(0, vpMatrix); } // for each player bullet for (int k = 0; k < playerBulletGenerator.size(); ++k) { BulletPatternGenerator& tmpBullet = playerBulletGenerator[k]; // for each wall for (int l = 0; l < level.wallGrid.size(); ++l) { Actor* tmpActor = &level.wallGrid[l]; if ( tmpBullet.chkCollision(tmpActor->getPosition(), tmpActor->getCollisionSize()) ) { // bullet hit wall - bullet will kill itself anyway } } // for each enemy for (int l = 0; l < enemyActorArr.size(); ++l) { Actor* tmpActor = &enemyActorArr[l]; if (!tmpActor->getIsDead()) if ( tmpBullet.chkCollision(tmpActor->getPosition(), tmpActor->getCollisionSize()) ) { // bullet hit enemy - hurt it tmpActor->TakeDamage(50.0f); if (tmpActor->getIsDead()) { // add kill count playerActor[0].otherKilled++; } } } // for each feeder for (int l = 0; l < feederActorArr.size(); ++l) { Actor* tmpActor = &feederActorArr[l]; if (!tmpActor->getIsDead()) if ( tmpBullet.chkCollision(tmpActor->getPosition(), tmpActor->getCollisionSize()) ) { // bullet hit enemy - hurt it tmpActor->TakeDamage(50.0f); if (tmpActor->getIsDead()) { // add kill count playerActor[0].otherKilled++; } } } // for each spawner for (int l = 0; l < spawnerActorArr.size(); ++l) { Actor* tmpActor = &spawnerActorArr[l]; if (!tmpActor->getIsDead()) if ( tmpBullet.chkCollision(tmpActor->getPosition(), tmpActor->getCollisionSize()) ) { // bullet hit enemy - hurt it tmpActor->TakeDamage(30.0f); if (tmpActor->getIsDead()) { // add kill count playerActor[0].spawnerKilled++; } } } // for each turret for (int l = 0; l < turretActorArr.size(); ++l) { Actor* tmpActor = &turretActorArr[l]; if (!tmpActor->getIsDead()) if ( tmpBullet.chkCollision(tmpActor->getPosition(), tmpActor->getCollisionSize()) ) { // bullet hit enemy - hurt it tmpActor->TakeDamage(50.0f); if (tmpActor->getIsDead()) { // add kill count playerActor[0].turretKilled++; } } } } // for each enemy bullet for (int k = 0; k < enemyBulletGenerator.size(); ++k) { BulletPatternGenerator& tmpBullet = enemyBulletGenerator[k]; // for each wall for (int l = 0; l < level.wallGrid.size(); ++l) { Actor* tmpActor = &level.wallGrid[l]; if ( tmpBullet.chkCollision(tmpActor->getPosition(), tmpActor->getCollisionSize()) ) { // bullet hit wall - bullet will kill itself anyway } } // for each player for (int l = 0; l < playerActor.size(); ++l) { int grazeCount = 0; int hitCount = 0; PlayerPawn* tmpActor = &playerActor[l]; if (!tmpActor->getIsDead()) tmpBullet.chkGrazeCollision(tmpActor->getPosition(), tmpActor->getCollisionSize(), tmpActor->grazeRadius, grazeCount, hitCount); if (hitCount > 0) { // bullet hit player - hurt it TODO tmpActor->TakeDamage(hitCount * 5.0f); } if (grazeCount > 0) { // bullet graze player - add XP float xp = (float)grazeCount * 40.0f * deltaTime; tmpActor->gainXP(xp); vec3f tmppos = tmpActor->getPosition(); tmppos.z += 25.0f; SparkleActor.setPosition(tmppos); SparkleActor.update(deltaTime); SparkleActor.draw(0, vpMatrix); } } } ///// feedback update LvUpActor.update(deltaTime); LvUpActor.draw(0, vpMatrix); ///// // draw panel for text m_drawRect->draw(tex_panel, 0, 0, 250, 270, color); // start rendering bitfont text overlaid here. t_renderer->startDrawText(); // draw HP text { std::string tmpStr; tmpStr.append( "HP : " ); tmpStr.append( lexical_cast<std::string, int>( playerActor[0].HP ) ); tmpStr.append( "/" ); tmpStr.append( lexical_cast<std::string, int>( playerActor[0].maxHP ) ); t_renderer->setText(txt_playerHP, tmpStr.c_str()); t_renderer->draw(txt_playerHP); } // draw XP text { std::string tmpStr; tmpStr.append( "XP : " ); tmpStr.append( lexical_cast<std::string, int>( playerActor[0].XP ) ); tmpStr.append( "/" ); tmpStr.append( lexical_cast<std::string, int>( playerActor[0].nextLV_XP ) ); t_renderer->setText(txt_playerXP, tmpStr.c_str()); t_renderer->draw(txt_playerXP); } // draw LV text { std::string tmpStr; tmpStr.append( "LV : " ); tmpStr.append( lexical_cast<std::string, int>( playerActor[0].LV ) ); if ( playerActor[0].LV == playerActor[0].maxLV ) tmpStr.append( " (MAX)" ); t_renderer->setText(txt_playerLV, tmpStr.c_str()); t_renderer->draw(txt_playerLV); } // draw control text { std::string tmpStr; switch (InputController::currentControlType) { case CONTROLTYPE_ORIGINAL : tmpStr.append("Original"); break; case CONTROLTYPE_MOVINGCENTER : tmpStr.append("MovingCenter"); break; case CONTROLTYPE_PLAYERCENTER : tmpStr.append("PlayerCenter"); break; case CONTROLTYPE_SWIPE : tmpStr.append("Swipe"); break; default : break; } t_renderer->setText(txt_playerControl, tmpStr.c_str()); t_renderer->draw(txt_playerControl); } // draw clock { t_renderer->draw(txt_clockText); // we update the clock after drawing to ensure it will change on pause int mins = mTimeVal / 60; float secs = (float)mTimeVal - mins*60; char str[32]; sprintf(str, "%03d:%05.2f", mins, secs); t_renderer->setText(txt_clockText, str); } // done rendering overlaid text. t_renderer->endDrawText(); // draw controller texture vec4f buttonA = InputController::posButtonA; vec4f buttonB = InputController::posButtonB; vec4f controllerBase = InputController::posControllerBase; vec4f controllerStick = InputController::posControllerStick; switch (InputController::currentControlType) { case CONTROLTYPE_ORIGINAL : { m_drawRect->draw(tex_baseController, controllerBase.x, controllerBase.z, controllerBase.y, controllerBase.w, color); m_drawRect->draw(tex_stickController, controllerStick.x, controllerStick.z, controllerStick.y, controllerStick.w, color); m_drawRect->draw(tex_ButtonA, buttonA.x, buttonA.z, buttonA.y, buttonA.w, color); m_drawRect->draw(tex_ButtonB, buttonB.x, buttonB.z, buttonB.y, buttonB.w, color); break; } case CONTROLTYPE_MOVINGCENTER : { m_drawRect->draw(tex_baseController, controllerBase.x, controllerBase.z, controllerBase.y, controllerBase.w, color); m_drawRect->draw(tex_stickController, controllerStick.x, controllerStick.z, controllerStick.y, controllerStick.w, color); m_drawRect->draw(tex_ButtonA, buttonA.x, buttonA.z, buttonA.y, buttonA.w, color); m_drawRect->draw(tex_ButtonB, buttonB.x, buttonB.z, buttonB.y, buttonB.w, color); break; } case CONTROLTYPE_PLAYERCENTER : { m_drawRect->draw(tex_ButtonA, buttonA.x, buttonA.z, buttonA.y, buttonA.w, color); m_drawRect->draw(tex_ButtonB, buttonB.x, buttonB.z, buttonB.y, buttonB.w, color); break; } case CONTROLTYPE_SWIPE : { m_drawRect->draw(tex_ButtonA, buttonA.x, buttonA.z, buttonA.y, buttonA.w, color); m_drawRect->draw(tex_ButtonB, buttonB.x, buttonB.z, buttonB.y, buttonB.w, color); break; } default : break; } glDisable(GL_BLEND); m_soundPlayer->update(); mEgl->swap(); renderer->swapBuffer(); lastFrameTime = Time::GetAbsoluteTimeSeconds(); // check reach exit? vec3f tmpVec = level.exitGrid[0].getPosition(); if (playerActor[0].chkCollision(tmpVec, level.exitGrid[0].getCollisionSize())) { // reach exit - load new level loadNewLevel(); } return true; } void Engine::updateFrame(bool interactible, long deltaTime) { if (interactible) { // Each frame, we check to see if the window has resized. While the // various events we get _should_ cover this, in practice, it appears // that the safest move across all platforms and OSes is to check at // the top of each frame checkWindowResized(); // Time stands still when we're auto-paused, and we don't // automatically render if (mGameplayMode) { advanceTime(deltaTime); // This will try to set up EGL if it isn't set up // When we first set up EGL completely, we also load our GLES resources // If these are already set up or we succeed at setting them all up now, then // we go ahead and render. renderFrame(true); } else if (isForcedRenderPending()) // forced rendering when needed for UI, etc { renderFrame(true); } } else { // Even if we are not interactible, we may be visible, so we // HAVE to do any forced renderings if we can. We must also // check for resize, since that may have been the point of the // forced render request in the first place! if (isForcedRenderPending() && mEgl->isReadyToRender(false)) { checkWindowResized(); renderFrame(false); } } } }
Rendering Code Sample
#pragma once #include <string> #include <vector> #include "util.h" namespace banknamespace { class Renderer { public: Renderer(); virtual ~Renderer(void); virtual void init(std::vector<void*> args ) = 0; virtual void clear() = 0; virtual void swapBuffer() = 0; virtual unsigned int loadTexture( const char* szFilePath ) = 0; virtual void clearAllTextures() = 0; virtual unsigned int loadMemoryTexture( unsigned char* pixels, int width, int height ) = 0; virtual void bindTexture( unsigned int textureState, unsigned int textureId ) = 0; virtual void disableTexture( unsigned int textureState ) = 0; virtual int createProgram(const char* programName, std::vector<attrStruct>& attrString, std::vector<const char*>& uniformString) = 0; virtual void useProgram(int progID) = 0; virtual int getLocation(const char* name) = 0; virtual void setVariable(int ulocation, shaderType type, void* value) = 0; virtual unsigned int createBuffer( void* start, int bufferSize, bufferType type ) = 0; virtual void drawIndex(unsigned int idVBO, unsigned int idIBO, int strideSize, unsigned int startVertex, unsigned int endVertex, int indexCount, int startIndex, int baseVertex) = 0; virtual void drawRect(unsigned int tex, void* mvp, void* vertpos) = 0; virtual void* getAndroidApp() = 0; }; }
#ifndef __ANDROIDGLRENDERER_H #define __ANDROIDGLRENDERER_H #include <cassert> #include <vector> #include <cstdarg> #include <nv_and_util/nv_native_app_glue.h> #include <nv_egl_util/nv_egl_util.h> #include <nv_glesutil/nv_draw_rect.h> #include "util.h" #include "ShaderManagerOpenGL.h" #include "Renderer.h" #include "drawVertex.h" #define MAX_LOADSTRING 100 namespace banknamespace { class AndroidGLrenderer : public Renderer { public: void* a_app; android_app* mApp; NvEGLUtil* egl; drawVertex *m_drawRect; AndroidGLrenderer(); ~AndroidGLrenderer(void); void init( std::vector<void*> args ); // GL method void clear(); void swapBuffer(); unsigned int loadTexture( const char* szFilePath ); void clearAllTextures(); unsigned int loadMemoryTexture( unsigned char* pixels, int width, int height ); void bindTexture( unsigned int textureState, unsigned int textureId ); void disableTexture( unsigned int textureState ); int createProgram(const char* programName, std::vector<attrStruct>& attrString, std::vector<const char*>& uniformString); void useProgram(int progID); int getLocation(const char* name); void setVariable(int ulocation, shaderType type, void* value); unsigned int createBuffer( void* start, int bufferSize, bufferType type ); void drawIndex(unsigned int idVBO, unsigned int idIBO, int strideSize, unsigned int startVertex, unsigned int endVertex, int indexCount, int startIndex, int baseVertex); void drawRect(unsigned int tex, void* mvp, void* vertpos); void* getAndroidApp(); protected: ShaderManagerOpenGL shaderManager; std::vector<unsigned int> textureIdArr; void InitializeOpenGL(); void ShutdownOpenGL(); }; } #endif // __ANDROIDGLRENDERER_H
#pragma once #include <cassert> #include <vector> #include "ShaderManagerOpenGL.h" #include "Resource.h" #include "util.h" #include "Renderer.h" #include "drawVertex.h" #include "IL/il.h" #pragma comment (lib, "DevIL.lib") #define MAX_LOADSTRING 100 namespace banknamespace { class OpenGLrenderer : public Renderer { public: HDC* m_hdcWindow; HGLRC m_renderingContext; drawVertex *m_drawRect; OpenGLrenderer(); ~OpenGLrenderer(void); void init( std::vector<void*> args ); // GL method void clear(); void swapBuffer(); unsigned int loadTexture( const char* szFilePath ); void clearAllTextures(); unsigned int loadMemoryTexture( unsigned char* pixels, int width, int height ); void bindTexture( unsigned int textureState, unsigned int textureId ); void disableTexture( unsigned int textureState ); int createProgram(const char* programName, std::vector<attrStruct>& attrString, std::vector<const char*>& uniformString); void useProgram(int progID); int getLocation(const char* name); void setVariable(int ulocation, shaderType type, void* value); unsigned int createBuffer( void* start, int bufferSize, bufferType type ); void drawIndex(unsigned int idVBO, unsigned int idIBO, int strideSize, unsigned int startVertex, unsigned int endVertex, int indexCount, int startIndex, int baseVertex); void drawRect(unsigned int tex, void* mvp, void* vertpos); void* getAndroidApp(); protected: ShaderManagerOpenGL shaderManager; std::vector<unsigned int> textureIdArr; void InitializeOpenGL(); void ShutdownOpenGL(); }; }
#ifndef _DRAW_VERT_H #define _DRAW_VERT_H #include <string> #include "Define.h" #ifdef _GLWINDOWS #ifdef _WIN32 #endif #elif defined(_NVANDROID) #include <GLES2/gl2.h> #endif class drawVertex { public: drawVertex(); ~drawVertex(); static void setScreenResolution(int w, int h); // A GLES context must be bound when calling this, and it must be // the same GLES context for all calls. If you need to delete the // GLES context, you must either delete all instances, or call // releaseGLES void draw(GLint tex, void* mvp, void* vertices, const float color[4]); // Most apps can avoid this if they always delete their NvDrawRects // when they lose their GLES context static void releaseGLES(); protected: class RectShader { public: RectShader(const char* vertText, const char* fragText); void bindShader(void* mvp, void* vert, const float color[4]); GLint m_program; GLint m_mvp; GLint m_destRectUniform; GLint m_colorUniform; GLint m_posAttrib; GLint m_uvAttrib; }; static void initGLES(); static int draw_glsl_log(GLuint obj, GLenum check_compile); static GLint draw_load_program_from_strings(const char *vertStr, const char *fragStr); static bool ms_glesInitialized; static RectShader* ms_shaderTex; static RectShader* ms_shaderColor; static float ms_screenWidth; static float ms_screenHeight; static int ms_instanceCount; }; #endif // _DRAW_VERT_H
#include "StdAfx.h" #include "drawVertex.h" #include "Define.h" #ifdef _GLWINDOWS #ifdef _WIN32 #endif #elif defined(_NVANDROID) #include <GLES2/gl2ext.h> #endif bool drawVertex::ms_glesInitialized = false; drawVertex::RectShader* drawVertex::ms_shaderTex = NULL; drawVertex::RectShader* drawVertex::ms_shaderColor = NULL; float drawVertex::ms_screenWidth = 1.0f; float drawVertex::ms_screenHeight = 1.0f; int drawVertex::ms_instanceCount = 0; static GLushort indices [4] = { 2, 3, 1, 0 }; static GLfloat vertices[8] = { 100.0f, 0.0f, 0.0f, 0.0f, 100.0f, 100.0f, 0.0f, 100.0f }; static GLfloat uvs [8] = { 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f }; static const char s_vertShader[] = "uniform mat4 mvp;\n" "uniform vec4 uDestRect;\n" "attribute vec2 aPos;\n" "attribute vec2 aUV;\n" "varying vec2 vTexCoord;\n" "void main() {\n" " vec2 pos = mix(uDestRect.xy, uDestRect.zw, aPos);\n" " vec4 tmp = vec4(aPos.x, 0.0, aPos.y,1.0);\n" " gl_Position = mvp * tmp;\n" " vTexCoord = aUV;\n" "}\n"; static const char s_fragShaderTex[] = "precision lowp float;\n" "uniform sampler2D uTex;\n" "varying vec2 vTexCoord;\n" "uniform vec4 uColor;\n" "void main() {\n" " gl_FragColor = uColor * texture2D(uTex, vTexCoord);\n" "}\n"; static const char s_fragShaderColor[] = "precision lowp float;\n" "varying vec2 vTexCoord;\n" "uniform vec4 uColor;\n" "void main() {\n" " gl_FragColor = uColor;\n" "}\n"; int drawVertex::draw_glsl_log(GLuint obj, GLenum check_compile) { if (check_compile == GL_COMPILE_STATUS) { int len = 0; glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &len); if(len > 0) { char *str = (char *) malloc(len * sizeof(char)); if (str) { glGetShaderInfoLog(obj, len, NULL, str); free(str); } } } else { int len = 0; glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &len); if(len > 0) { char *str = (char *)malloc(len * sizeof(char)); if (str) { glGetProgramInfoLog(obj, len, NULL, str); free(str); } } } return 0; } GLint drawVertex::draw_load_program_from_strings(const char *vertStr, const char *fragStr) { const char* shaders[2]; int sizes[2]; GLuint vert = glCreateShader(GL_VERTEX_SHADER); GLuint frag = glCreateShader(GL_FRAGMENT_SHADER); GLuint program = glCreateProgram(); glAttachShader(program, vert); glAttachShader(program, frag); if(vertStr) { shaders[0] = vertStr; sizes[0] = strlen(shaders[0]); } else { return (GLint)0; } glShaderSource(vert, 1, (const char **) shaders, sizes); glCompileShader(vert); draw_glsl_log(vert, GL_COMPILE_STATUS); if(fragStr) { shaders[0] = fragStr; sizes[0] = strlen(shaders[0]); } else { return (GLint)0; } glShaderSource(frag, 1, (const char **) shaders, sizes); glCompileShader(frag); draw_glsl_log(frag, GL_COMPILE_STATUS); glLinkProgram(program); draw_glsl_log(program, GL_LINK_STATUS); return program; } drawVertex::RectShader::RectShader(const char* vertText, const char* fragText) { m_program = draw_load_program_from_strings(vertText, fragText); glUseProgram(m_program); m_mvp = glGetUniformLocation(m_program, "mvp"); m_destRectUniform = glGetUniformLocation(m_program, "uDestRect"); m_colorUniform = glGetUniformLocation(m_program, "uColor"); m_posAttrib = glGetAttribLocation(m_program, "aPos"); m_uvAttrib = glGetAttribLocation(m_program, "aUV"); GLint texUni = glGetUniformLocation(m_program, "uTex"); if (texUni != -1) glUniform1i(texUni, 0); } void drawVertex::RectShader::bindShader(void* mvp, void* vert, const float color[4]) { glUseProgram(m_program); glBindBuffer(GL_ARRAY_BUFFER, 0); glVertexAttribPointer(m_posAttrib, 2, GL_FLOAT, GL_FALSE, 0, vert); glEnableVertexAttribArray(m_posAttrib); if (m_uvAttrib != -1) { glVertexAttribPointer(m_uvAttrib, 2, GL_FLOAT, GL_FALSE, 0, uvs); glEnableVertexAttribArray(m_uvAttrib); } glUniformMatrix4fv( m_mvp, 1, 0, reinterpret_cast<float*>( mvp ) ); glUniform4fv(m_colorUniform, 1, color); } drawVertex::drawVertex() { ms_instanceCount++; } drawVertex::~drawVertex() { ms_instanceCount--; if (!ms_instanceCount) releaseGLES(); } void drawVertex::initGLES() { ms_glesInitialized = true; ms_shaderTex = new RectShader(s_vertShader, s_fragShaderTex); ms_shaderColor = new RectShader(s_vertShader, s_fragShaderColor); } void drawVertex::releaseGLES() { ms_glesInitialized = false; delete ms_shaderTex; delete ms_shaderColor; ms_shaderTex = NULL; ms_shaderColor = NULL; } void drawVertex::setScreenResolution(int w, int h) { ms_screenWidth = (float)w; ms_screenHeight = (float)h; } void drawVertex::draw(GLint tex, void* mvp, void* vertices, const float color[4]) { RectShader* shader = tex ? ms_shaderTex : ms_shaderColor; if (!ms_glesInitialized) { initGLES(); shader = tex ? ms_shaderTex : ms_shaderColor; if (!shader) return; } shader->bindShader(mvp, vertices, color); if (tex) { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); } glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, indices); }
Support Classes Code Sample
#ifndef __EGLINTERFACE_H #define __EGLINTERFACE_H #include "Define.h" #include <vector> namespace banknamespace { class EGLInterface { public: EGLInterface(); ~EGLInterface(); void init( std::vector<void*> args ); void setWidth(int w); void setHeight(int h); int getWidth(); int getHeight(); bool setWindow(void* window); bool checkWindowResized(); bool isReadyToRender(bool allocateIfNeeded = false); bool swap(); void* egl; protected: int width; int height; }; } #endif
#include "StdAfx.h" #include "EGLInterface.h" #ifdef _GLWINDOWS namespace banknamespace { EGLInterface::EGLInterface() { width = 0; height = 0; egl = 0; } EGLInterface::~EGLInterface() { } void EGLInterface::init( std::vector<void*> args ) { ///// args[0] = void* egl egl = args[0]; } void EGLInterface::setWidth(int w) { this->width = w; } void EGLInterface::setHeight(int h) { this->height = h; } int EGLInterface::getWidth() { return this->width; } int EGLInterface::getHeight() { return this->height; } bool EGLInterface::setWindow(void* window) { return true; } bool EGLInterface::checkWindowResized() { return false; } bool EGLInterface::isReadyToRender(bool allocateIfNeeded) { return true; } bool EGLInterface::swap() { return true; } } #elif defined(_NVANDROID) #include <nv_egl_util/nv_egl_util.h> namespace banknamespace { EGLInterface::EGLInterface() { width = 0; height = 0; egl = 0; } EGLInterface::~EGLInterface() { } void EGLInterface::init( std::vector<void*> args ) { ///// args[0] = void* egl egl = args[0]; } void EGLInterface::setWidth(int w) { this->width = w; } void EGLInterface::setHeight(int h) { this->height = h; } int EGLInterface::getWidth() { NvEGLUtil* eglutil = reinterpret_cast<NvEGLUtil*>( egl ); return eglutil->getWidth(); } int EGLInterface::getHeight() { NvEGLUtil* eglutil = reinterpret_cast<NvEGLUtil*>( egl ); return eglutil->getHeight(); } bool EGLInterface::setWindow(void* window) { NvEGLUtil* eglutil = reinterpret_cast<NvEGLUtil*>( egl ); ANativeWindow* win = reinterpret_cast<ANativeWindow*>( window ); bool tmp = eglutil->setWindow(win); return tmp; } bool EGLInterface::checkWindowResized() { NvEGLUtil* eglutil = reinterpret_cast<NvEGLUtil*>( egl ); return eglutil->checkWindowResized(); } bool EGLInterface::isReadyToRender(bool allocateIfNeeded) { NvEGLUtil* eglutil = reinterpret_cast<NvEGLUtil*>( egl ); return eglutil->isReadyToRender(allocateIfNeeded); } bool EGLInterface::swap() { NvEGLUtil* eglutil = reinterpret_cast<NvEGLUtil*>( egl ); return eglutil->swap(); } } #else #error implementation missing #endif
#ifndef __SOUNDPLAYER_H #define __SOUNDPLAYER_H #include "Define.h" #include <vector> namespace banknamespace { class SoundPlayer { public: SoundPlayer(); ~SoundPlayer(); unsigned int init(std::vector<void*> args ); // returns true if the audio is playing, false if not bool isPlaying(unsigned int id); // starts playback on true, stops on false // Playback is currently looped bool play(unsigned int id, bool play); bool setLoop(unsigned int id, bool isLoop); bool setVolume(unsigned int id, int millibel); void update(); protected: void* soundSystem; std::vector<float> volume; std::vector<void*> soundPointerId; }; } #endif
#include "StdAfx.h" #include "SoundPlayer.h" #ifdef _GLWINDOWS #include "fmod.hpp" #include "fmod_errors.h" #pragma comment(lib, "fmodex_vc.lib") FMOD::Channel* channels[32]; namespace banknamespace { SoundPlayer::SoundPlayer() { FMOD::System* system; FMOD_RESULT result = FMOD::System_Create( &system ); result = system->init(32, FMOD_INIT_NORMAL, 0); soundSystem = system; } SoundPlayer::~SoundPlayer() { FMOD::Sound* sound; for (int i = 0; i < soundPointerId.size(); ++i) { sound = reinterpret_cast<FMOD::Sound*>( soundPointerId[i] ); sound->release(); } } unsigned int SoundPlayer::init( std::vector<void*> args ) { ///// args[0] = const char* filename ///// args[1] = AAssetManager* assetManager std::string tmpStr = "assets/"; tmpStr.append(reinterpret_cast<const char*>( args[0] )); FMOD::Sound* sound; FMOD::System* system = reinterpret_cast<FMOD::System*>( soundSystem ); FMOD_RESULT result = system->createStream(tmpStr.c_str(), FMOD_HARDWARE | FMOD_2D, 0, &sound); sound->setMode(FMOD_HARDWARE | FMOD_2D); soundPointerId.push_back(sound); return soundPointerId.size()-1; } // returns true if the audio is playing, false if not bool SoundPlayer::isPlaying(unsigned int id) { return false; } // starts playback on true, stops on false // Playback is currently looped bool SoundPlayer::play(unsigned int id, bool play) { FMOD::Sound* sound = reinterpret_cast<FMOD::Sound*>( soundPointerId[id] ); FMOD::System* system = reinterpret_cast<FMOD::System*>( soundSystem ); if (play) { system->playSound(FMOD_CHANNEL_FREE, sound, false, &channels[id]); channels[id]->setVolume(volume[id]); channels[id]->setPaused(false); } else { channels[id]->stop(); } return true; } bool SoundPlayer::setLoop(unsigned int id, bool isLoop) { FMOD::Sound* sound = reinterpret_cast<FMOD::Sound*>( soundPointerId[id] ); if (isLoop) sound->setMode(FMOD_HARDWARE | FMOD_LOOP_NORMAL | FMOD_2D); return true; } bool SoundPlayer::setVolume(unsigned int id, int millibel) { volume.push_back( millibel / 100.0f ); return true; } void SoundPlayer::update() { FMOD::System* system = reinterpret_cast<FMOD::System*>( soundSystem ); system->update(); } } #elif defined(_NVANDROID) #include "NvSLESPlayer.h" namespace banknamespace { SoundPlayer::SoundPlayer() { } SoundPlayer::~SoundPlayer() { for (int i = 0; i < soundPointerId.size(); ++i) { delete soundPointerId[i]; } } unsigned int SoundPlayer::init( std::vector<void*> args ) { ///// args[0] = const char* filename ///// args[1] = AAssetManager* assetManager const char* filename = reinterpret_cast<const char*>( args[0] ); AAssetManager* assetManager = reinterpret_cast<AAssetManager*>( args[1] ); NvSLESPlayer* sptr = NvSLESPlayer::create(assetManager, filename); soundPointerId.push_back(sptr); return soundPointerId.size()-1; } // returns true if the audio is playing, false if not bool SoundPlayer::isPlaying(unsigned int id) { NvSLESPlayer* tmp = reinterpret_cast<NvSLESPlayer*>( soundPointerId[id] ); return tmp->isPlaying(); } // starts playback on true, stops on false // Playback is currently looped bool SoundPlayer::play(unsigned int id, bool play) { NvSLESPlayer* tmp = reinterpret_cast<NvSLESPlayer*>( soundPointerId[id] ); return tmp->play(play); } bool SoundPlayer::setLoop(unsigned int id, bool isLoop) { NvSLESPlayer* tmp = reinterpret_cast<NvSLESPlayer*>( soundPointerId[id] ); return tmp->setLoop(isLoop); } bool SoundPlayer::setVolume(unsigned int id, int millibel) { NvSLESPlayer* tmp = reinterpret_cast<NvSLESPlayer*>( soundPointerId[id] ); return tmp->setVolume(millibel); } void SoundPlayer::update() { } } #else #error implementation missing #endif
Input Controller Code Sample
#pragma once //#include "KeyboardInput.h" //#include "MouseInput.h" #include "util.h" #include <cmath> #include <vector> #include "Pawn.h" #include "NvMultiInput.h" #include "Define.h" namespace banknamespace { enum controlType { CONTROLTYPE_ORIGINAL, CONTROLTYPE_MOVINGCENTER, CONTROLTYPE_PLAYERCENTER, CONTROLTYPE_SWIPE, CONTROLTYPE_LAST }; class InputController { public: // variable static bool isInit; static bool isControlChanged; static Pawn* player; static vec3f accel; static float width; static float height; static float buttonSizeWidth; static float buttonSizeHeight; static float controllerSizeWidth; static float controllerSizeHeight; static bool touchState[10]; static bool buttonState[5]; static vec4f posButtonA; static vec4f posButtonB; static vec4f posControllerBase; static vec4f posControllerStick; static vec2f centerBase; static vec2f centerStick; static controlType currentControlType; // end of variable static void initVar( std::vector<void*> args ); //static void updateAccelerometer(float x, float y, float z); static void updateTouchState(NvMultiInput *start); static void updateTouch(NvMultiInput *nvInput); static void updateNoTouch(); ///// control scheme here static void directionOriginal(NvMultiInput *nvInput); static void directionPlayerCenter(NvMultiInput *nvInput); static void directionMovingCenter(NvMultiInput *nvInput); static void directionSwipe(NvMultiInput *nvInput); static bool virtualButtonAB(NvMultiInput *nvInput); static void virtualButtonKeyboard(bool buttons[6]); static void updateFrame(); }; }
#include "StdAfx.h" #include "InputController.h" #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "InputController.cpp", __VA_ARGS__)) namespace banknamespace { float InputController::buttonSizeWidth = 150.0f; float InputController::buttonSizeHeight = 150.0f; float InputController::controllerSizeWidth = 300.0f; float InputController::controllerSizeHeight = 300.0f; Pawn* InputController::player = 0; vec3f InputController::accel; float InputController::width; float InputController::height; bool InputController::touchState[10]; bool InputController::buttonState[5]; controlType InputController::currentControlType; vec4f InputController::posButtonA; vec4f InputController::posButtonB; vec4f InputController::posControllerBase; vec4f InputController::posControllerStick; vec2f InputController::centerBase; vec2f InputController::centerStick; bool InputController::isInit = false; bool InputController::isControlChanged = true; void InputController::initVar( std::vector<void*> args ) { } //void InputController::updateAccelerometer(float x, float y, float z) //{ // // x,y,z range of -10 to 10; // accel.x += -x / 10.0f; // accel.y += -y / 10.0f; // accel.z += -z / 10.0f; //} void InputController::updateTouchState(NvMultiInput *start) { for (int i = 0; i < 10; ++i) { touchState[i] = false; } for (int i = 0; i < 5; ++i) { buttonState[i] = false; } NvMultiInput *td = start->getNextTouch(NULL); while (td) { const int id = td->getId(); touchState[id] = true; td = start->getNextTouch(td); } } void InputController::updateNoTouch() { if (!buttonState[4]) { centerStick = centerBase; posControllerStick.x = centerStick.x - controllerSizeWidth / 4.0f; posControllerStick.y = centerStick.x + controllerSizeWidth / 4.0f; posControllerStick.z = centerStick.y - controllerSizeHeight / 4.0f; posControllerStick.w = centerStick.y + controllerSizeHeight / 4.0f; } } void InputController::updateTouch(NvMultiInput *nvInput) { switch (currentControlType) { case CONTROLTYPE_ORIGINAL : { if (virtualButtonAB(nvInput)) break; directionOriginal(nvInput); break; } case CONTROLTYPE_MOVINGCENTER : { if (virtualButtonAB(nvInput)) break; directionMovingCenter(nvInput); break; } case CONTROLTYPE_PLAYERCENTER : { if (virtualButtonAB(nvInput)) break; directionPlayerCenter(nvInput); break; } case CONTROLTYPE_SWIPE : { if (virtualButtonAB(nvInput)) break; directionSwipe(nvInput); break; } default : break; } } void InputController::directionOriginal(NvMultiInput *nvInput) { centerBase.x = 30 + controllerSizeWidth / 2.0f; centerBase.y = height - 30 - controllerSizeHeight / 2.0f; posControllerBase.x = centerBase.x - controllerSizeWidth / 2.0f; posControllerBase.y = centerBase.x + controllerSizeWidth / 2.0f; posControllerBase.z = centerBase.y - controllerSizeHeight / 2.0f; posControllerBase.w = centerBase.y + controllerSizeHeight / 2.0f; if ( (nvInput->x > posControllerBase.x - 100 && nvInput->x < posControllerBase.y + 200) && (nvInput->y > posControllerBase.z - 200 && nvInput->y < posControllerBase.w + 100) ) { // move stick in AOE vec3f tmp(nvInput->x - centerBase.x, nvInput->y - centerBase.y, 0.0f); float sizevec = getSize(tmp); Normalize(tmp); float maxSize = controllerSizeWidth/2.0f; if (sizevec > maxSize) sizevec = maxSize; tmp = tmp * sizevec; centerStick.x = centerBase.x + tmp.x; centerStick.y = centerBase.y + tmp.y; posControllerStick.x = centerStick.x - controllerSizeWidth / 4.0f; posControllerStick.y = centerStick.x + controllerSizeWidth / 4.0f; posControllerStick.z = centerStick.y - controllerSizeHeight / 4.0f; posControllerStick.w = centerStick.y + controllerSizeHeight / 4.0f; buttonState[4] = true; } } void InputController::directionPlayerCenter(NvMultiInput *nvInput) { centerBase.x = width / 2.0f; centerBase.y = height / 2.0f; posControllerBase.x = centerBase.x - controllerSizeWidth / 2.0f; posControllerBase.y = centerBase.x + controllerSizeWidth / 2.0f; posControllerBase.z = centerBase.y - controllerSizeHeight / 2.0f; posControllerBase.w = centerBase.y + controllerSizeHeight / 2.0f; if ( (nvInput->x > posControllerBase.x - 2000 && nvInput->x < posControllerBase.y + 2000) && (nvInput->y > posControllerBase.z - 2000 && nvInput->y < posControllerBase.w + 2000) ) { // move stick in AOE vec3f tmp(nvInput->x - centerBase.x, nvInput->y - centerBase.y, 0.0f); float sizevec = getSize(tmp); Normalize(tmp); float maxSize = controllerSizeWidth/2.0f; if (sizevec > maxSize) sizevec = maxSize; tmp = tmp * sizevec; centerStick.x = centerBase.x + tmp.x; centerStick.y = centerBase.y + tmp.y; posControllerStick.x = centerStick.x - controllerSizeWidth / 4.0f; posControllerStick.y = centerStick.x + controllerSizeWidth / 4.0f; posControllerStick.z = centerStick.y - controllerSizeHeight / 4.0f; posControllerStick.w = centerStick.y + controllerSizeHeight / 4.0f; buttonState[4] = true; } } void InputController::directionMovingCenter(NvMultiInput *nvInput) { if (!isInit) { centerBase.x = 30 + controllerSizeWidth / 2.0f; centerBase.y = height - 30 - controllerSizeHeight / 2.0f; posControllerBase.x = centerBase.x - controllerSizeWidth / 2.0f; posControllerBase.y = centerBase.x + controllerSizeWidth / 2.0f; posControllerBase.z = centerBase.y - controllerSizeHeight / 2.0f; posControllerBase.w = centerBase.y + controllerSizeHeight / 2.0f; isInit = true; } if ( (nvInput->x > posControllerBase.x - 200 && nvInput->x < posControllerBase.y + 200) && (nvInput->y > posControllerBase.z - 200 && nvInput->y < posControllerBase.w + 200) ) { // move stick in AOE vec3f tmp(nvInput->x - centerBase.x, nvInput->y - centerBase.y, 0.0f); float sizevec = getSize(tmp); Normalize(tmp); float maxSize = controllerSizeWidth/2.0f; if (sizevec > maxSize) sizevec = maxSize; tmp = tmp * sizevec; centerStick.x = nvInput->x; centerStick.y = nvInput->y; posControllerStick.x = centerStick.x - controllerSizeWidth / 4.0f; posControllerStick.y = centerStick.x + controllerSizeWidth / 4.0f; posControllerStick.z = centerStick.y - controllerSizeHeight / 4.0f; posControllerStick.w = centerStick.y + controllerSizeHeight / 4.0f; centerBase.x = centerStick.x - tmp.x; centerBase.y = centerStick.y - tmp.y; posControllerBase.x = centerBase.x - controllerSizeWidth / 2.0f; posControllerBase.y = centerBase.x + controllerSizeWidth / 2.0f; posControllerBase.z = centerBase.y - controllerSizeHeight / 2.0f; posControllerBase.w = centerBase.y + controllerSizeHeight / 2.0f; buttonState[4] = true; } } void InputController::directionSwipe(NvMultiInput *nvInput) { float diffX = nvInput->spdX; float diffY = nvInput->spdY; if (diffX > 1000.0f) diffX = 1000.0f; if (diffX < -1000.0f) diffX = -1000.0f; if (diffY > 1000.0f) diffY = 1000.0f; if (diffY < -1000.0f) diffY = -1000.0f; accel.x += -diffX / 1000.0f; accel.z += -diffY / 1000.0f; accel.y += 0.0f; } bool InputController::virtualButtonAB(NvMultiInput *nvInput) { // x, y = width // z, w = height posButtonA.y = width-10; posButtonA.x = posButtonA.y-buttonSizeWidth; posButtonA.w = height-10; posButtonA.z = posButtonA.w-buttonSizeHeight; posButtonB.y = posButtonA.x-10; posButtonB.x = posButtonB.y-buttonSizeWidth; posButtonB.w = height-10; posButtonB.z = posButtonB.w-buttonSizeHeight; if ( (nvInput->x > posButtonA.x && nvInput->x < posButtonA.y) && (nvInput->y > posButtonA.z && nvInput->y < posButtonA.w) ) { // lock direction buttonState[1] = true; return true; } if ( (nvInput->x > posButtonB.x && nvInput->x < posButtonB.y) && (nvInput->y > posButtonB.z && nvInput->y < posButtonB.w) ) { // shoot buttonState[0] = true; return true; } return false; } void InputController::virtualButtonKeyboard(bool buttons[6]) { // buttons = w s a d shoot lockFacing const float force = 3.0f; if (buttons[0]) { accel.z += force; } if (buttons[1]) { accel.z -= force; } if (buttons[2]) { accel.x += force; } if (buttons[3]) { accel.x -= force; } if (buttons[4]) { player->shoot(); } if (buttons[5]) { player->setLockDirection(true); } else { player->setLockDirection(false); } } void InputController::updateFrame() { #ifdef _GLWINDOWS #elif defined(_NVANDROID) if (buttonState[0]) { player->shoot(); } if (buttonState[1]) { player->setLockDirection( true ); } else { player->setLockDirection( false ); } #endif const float amplifier = 0.15f; const float stickModifier = 1.0f; switch (currentControlType) { case CONTROLTYPE_ORIGINAL : { vec3f tmp(centerStick.x - centerBase.x, centerStick.y - centerBase.y, 0.0f); float maxSize = controllerSizeWidth/2.0f; // apply impulse float diffX = tmp.x / maxSize; float diffY = tmp.y / maxSize; accel.x += -diffX / stickModifier; accel.z += -diffY / stickModifier; accel.y += 0.0f; break; } case CONTROLTYPE_MOVINGCENTER : { vec3f tmp(centerStick.x - centerBase.x, centerStick.y - centerBase.y, 0.0f); float maxSize = controllerSizeWidth/2.0f; // apply impulse float diffX = tmp.x / maxSize; float diffY = tmp.y / maxSize; accel.x += -diffX / stickModifier; accel.z += -diffY / stickModifier; accel.y += 0.0f; break; } case CONTROLTYPE_PLAYERCENTER : { vec3f tmp(centerStick.x - centerBase.x, centerStick.y - centerBase.y, 0.0f); float maxSize = controllerSizeWidth/2.0f; // apply impulse float diffX = tmp.x / maxSize; float diffY = tmp.y / maxSize; accel.x += -diffX / stickModifier; accel.z += -diffY / stickModifier; accel.y += 0.0f; break; } default : { break; } } vec3f tmp = accel; tmp.x = std::max(tmp.x, -100.0f); tmp.x = std::min(tmp.x, 100.0f); tmp.z = std::max(tmp.z, -100.0f); tmp.z = std::min(tmp.z, 100.0f); tmp.y = 0.0f; player->applyImpulseNoEuler(tmp * amplifier); // cap player velocity float speedCap = 3.0f; if (currentControlType == CONTROLTYPE_SWIPE) { speedCap = 3.5f; } vec3f velocity = player->getVelocity(); velocity.x = std::max(velocity.x, -speedCap); velocity.x = std::min(velocity.x, speedCap); velocity.z = std::max(velocity.z, -speedCap); velocity.z = std::min(velocity.z, speedCap); velocity.y = 0.0f; player->setVelocityNoEuler(velocity); accel = vec3f(); // change control int count = 0; for (int i = 0; i < 10; ++i) { if (touchState[i]) { count++; } } if (count >= 7) { if (!isControlChanged) { // change control isInit = false; int tmp = ((int)currentControlType+1) % (int)CONTROLTYPE_LAST; currentControlType = (controlType)tmp; isControlChanged = true; if (currentControlType == CONTROLTYPE_SWIPE) { player->actorCam.setDrag(5.0f); } else { player->actorCam.setDrag(15.0f); } } } else { isControlChanged = false; } } }