Pull to refresh

Comments 24

Вплоть до последнего ждал какого-то неожиданного хэппи-энда. Однако, чудеса бывают редко, а жаль.
Ну, опыт получен, игра в аппсторе лежит… Я считаю, это хэппи энд. А всех денег не заработать.

Блин, а где ссылка-то, или хотя бы какие то ключевые слова для поиска?))

Не хотел, чтобы статью сочли рекламной.
Но игра бесплатна уже, без встроенной рекламы и встроенных покупок… так что, наверное, можно?

eggsodus
Это сложный вопрос, мне надо поговорить с женой. Конечно, поеду, я давно мечтал поработать в Париже.
— Но есть нюансы, — сказал босс после короткой паузы.
— А?
— В Париж нельзя.
— Понял, — ответил я. Я ничего не понял.
— Поедешь в Лондон? Там поработаешь немного, и переведем тебя в Париж.
— Мне надо поговорить с женой, — ответил я. – Поеду.

Это шесть баллов по пятибалльной шкале! Серое дождливое утро стало чуть светлее, спасибо!

А в Париже снегопад. Очень редкое и очень радостное явление. Весь город в снегу!
Похоронить мы решили с выпуском последнего апдейта
не выложенное в опенсорс не считается допохороненным.
мне стыдно этот код выкладывать. Честно, не жалко, а стыдно ))
Дело хозяйское. Хотя, например, сорцы того же vp8 поначалу тоже вызвали бурю фейспальмов, но ничего, утряслось-улеглось.
Ага, статью, значит, не стыдно, а код стыдно, ай-ай-ай)
UFO just landed and posted this here

Отличный рассказ!
А почему не упомянули про разработку AI в движке — там тоже порядочно интересного должно было быть!
Текущее поведение правда немного напоминает пресловутых крисалидов…
P.S. И что за главная тема играет во время сражения? Тоже работа фрилансеров?
Очень напоминает по атмосферности что-то в духе первого Deus Ex.

Спасибо.
Действительно, совершенно забыл рассказать про AI. Правда, ничего особо интересного в нем нет, просто машина состояний. Единая для всех врагов. Враг перед ходом оценивает свое здоровье, сколько его сторонников рядом и сколько противников рядом, и либо атакует, либо бежит к ближайшему стороннику.
Высокоуровневые противники могу отправлять сообщение на сбор всех на подмогу, тогда низкоуровневые бросают все и бегут к высокоуровневому.
В общем, все довольно примитивно, и было написано довольно быстро.
Только листинг функции очень обьемный ))

По музыке, к сожалению, не подскажу, ей занимались Федор Михалыч и Димон.

Большой и страшненький кусок кода
void agProcessAI() {
    int friendsnear;//, foesnear; - self.visibleenemiescount
    int closestfoe, closestfriend, closestvisiblefoe;
    int closestfoedistance, closestfrienddistance, closestvisiblefoedistance;
    int proceedfalg;
    int currmodelnum;
    int unitsAlive = 0;
    // if there are animations, we do not proceed
    // we check if there are any active animations, like running or shooting
    proceedfalg = LEAF_YES;
    for (int i=0; i<agVars.enemyUnitsCount; i++) {
        // wait for ongoing animations to finish
        if(
           (agVars.enemyUnits[i].model.currAnimation != ANIMSTAND) &&
           (agVars.enemyUnits[i].model.currAnimation != ANIMSIT) &&
           (agVars.enemyUnits[i].model.currAnimation != ANIMDEAD)
           )
        {
            proceedfalg = LEAF_NO;
#ifdef DEBUGACTIONGAME
            leafLog(LEAF_NO, "AG: agProcessAI: waiting for enemyUnit[%i] to finish animation %i\n", i, agVars.enemyUnits[i].model.currAnimation);
#endif
        }
        if(agVars.enemyUnits[i].health > 0) {
            unitsAlive++;
        }
    }
    // if there are animations, finish method.
    if(!proceedfalg) {
        return;
    }
    // and if there are no active animations, we are free to act next
    // do some thinking
    switch (agVars.aiState) {
            
        case LEAFAIGLOBALSTATEINIT:
            // we need to reset any AI info left from previous turn
            for(int i=0; i<agVars.enemyUnitsCount; i++) {
                //agVars.enemyUnits[i].aiInbox = AIMESSAGENOMESSAGES;
                agVars.enemyUnits[i].aiState = LEAFAIACTIONINIT;
                agGetVisibilityFor(&agVars.enemyUnits[i], agVars.playerUnits, agVars.playerUnitsCount);
            }
            agVars.aiState = LEAFAIGLOBALACTION;
            //break;  // GLOBAL AI INIT - no need to break, we are ok to proceed with ACTION
        
        case LEAFAIGLOBALACTION:
            // main part. We check every model's AI state and do something
            // find first model to work on
            currmodelnum = -1;
            for(int i=0; i<agVars.enemyUnitsCount; i++) {
                if(agVars.enemyUnits[i].aiState != LEAFAIACTIONENDMOVE) {
                    currmodelnum = i;
                    break;
                }
            }
#ifdef DEBUGACTIONGAME
            leafLog(LEAF_NO, "AG: AI: ******\nprocessing enemy %i\n*******\n", currmodelnum);
#endif
            if(currmodelnum < 0) {// if there are no models to work on, set turn completion flag and return
#ifdef DEBUGACTIONGAME
                leafLog(LEAF_NO, "AG: AI: Done all, finishing\n");
#endif
                agVars.aiState = LEAFAIGLOBALCOMPLETED;
                return;
            }
            // check if we are alive
            if((agVars.enemyUnits[currmodelnum].health > 0) &&
               (agVars.enemyUnits[currmodelnum].TUs > 0) &&
               (agVars.enemyUnits[currmodelnum].aiState != LEAFAIACTIONENDMOVE))  // alive, has TUs and needs processing
            {
                // GET some location info...
#ifdef DEBUGACTIONGAME
                leafLog(LEAF_NO, "AG: AI: desciding what to do for enemy %i\n", currmodelnum);
#endif
                // first we findout how many friends and foes do we see
                friendsnear = 0;
                //foesnear = 0;
                agVars.enemyUnits[currmodelnum].visibleEnemiesCount = 0;
                closestfriend = -1;
                closestfoe = -1;
                closestvisiblefoe = -1;
                closestfoedistance = GAMEVIEWDISTSQARED*GAMEVIEWDISTSQARED;
                closestfrienddistance = GAMEVIEWDISTSQARED*GAMEVIEWDISTSQARED;
                closestvisiblefoedistance = GAMEVIEWDISTSQARED*GAMEVIEWDISTSQARED;
                for(int ii=0; ii<agVars.enemyUnitsCount; ii++) {
                    if((agVars.enemyUnits[ii].health > 0) && (ii != currmodelnum)) {  // if alive and not self
                        int distance = (int)agGetDistanceSQ(&agVars.enemyUnits[currmodelnum], &agVars.enemyUnits[ii]);
                        if(distance < closestfrienddistance) {
                            // this is closest friend so far
                            closestfrienddistance = distance;
                            closestfriend = ii;
                        }
                        if(distance < GAMEVIEWDISTSQARED) {
                            friendsnear++;
                        }
                    }// if alive
                }// for enemycount
                for(int ii=0; ii<agVars.playerUnitsCount; ii++) {
                    if(agVars.playerUnits[ii].health > 0) {
                        int distance = (int)agGetDistanceSQ(&agVars.enemyUnits[currmodelnum], &agVars.playerUnits[ii]);
                        if(distance < closestfoedistance) {
                            // this is closest foe so far
                            closestfoedistance = distance;
                            closestfoe = ii;
                            // can we see this foe?
                            if(agIsVisibleFor(&agVars.enemyUnits[currmodelnum], &agVars.playerUnits[ii])) {
                                agVars.enemyUnits[currmodelnum].visibleEnemiesCount++;
                                if(distance < closestvisiblefoedistance) {
                                    closestvisiblefoedistance = distance;
                                    closestvisiblefoe = ii;
                                }
                            }
                        }
                        /*
                        if(distance < GAMEVIEWDISTSQARED) {
                            agVars.enemyUnits[currmodelnum].visibleEnemiesCount++;
                        }
                         */
                    }// if alive
                }// for modelcount
#ifdef DEBUGACTIONGAME
                leafLog(LEAF_NO, "AG: AI: see %i friends and %i foes\n", friendsnear, agVars.enemyUnits[currmodelnum].visibleEnemiesCount);
#endif
                // what state are we in?
                switch (agVars.enemyUnits[currmodelnum].aiState) {
                        
                    case LEAFAIACTIONINIT:
                        // what to do?
//************************************************************************************
						// check messages
                        if(agVars.enemyUnits[currmodelnum].aiInbox != AIMESSAGENOMESSAGES) {
                            agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONCHECKMESSAGES;
                            return;
                        }
                        // call to arms?
                        if( (agVars.enemyUnits[currmodelnum].visibleEnemiesCount) && (enemy[agVars.enemyUnits[currmodelnum].type].callToArmsDistanceSQ > SMALLNUMBER) ) {
                            for(int i=0; i<agVars.enemyUnitsCount; i++) {
                                if(i == currmodelnum) {
                                    continue;
                                }
                                // check if a callee is close enough
                                if(agGetDistanceSQ(&agVars.enemyUnits[currmodelnum], &agVars.enemyUnits[i]) < enemy[agVars.enemyUnits[currmodelnum].type].callToArmsDistanceSQ) {
                                    agVars.enemyUnits[i].aiInbox = AIMESSAGECALLTOARMS;
                                    agVars.enemyUnits[i].aiSender = currmodelnum;
#ifdef DEBUGACTIONGAME
                                    agPrintToInfoboard(getLocalizedString("DE7"));
#endif
                                }
                            }
                        }
//************************************************************************************
                        // am I the last man standing?
                        if(unitsAlive < 2) {
#ifdef DEBUGACTIONGAME
                            leafLog(LEAF_NO, "AG: AI: I am the last man standing.... BERSERK\n");
                            agPrintToInfoboard(getLocalizedString("DE8"));
#endif
                            agVars.enemyUnits[currmodelnum].aiState = LEAFAIBERSERK;
                            return;

                        }
                        // if there are no foes near...
                        if(agVars.enemyUnits[currmodelnum].visibleEnemiesCount == 0) {
                            // if the closest enemy is very far, do nothing
                            if(closestfoedistance > (int)((float)GAMEVIEWDISTSQARED*GAMEPERCEPTIONDISTANCE)) {
#ifdef DEBUGACTIONGAME
                                leafLog(LEAF_NO, "AG: AI: enemies far, do nothing\n");
#endif
                                agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONDONOTHING;
                                return;
                            } else {   // closest enemy is close enough to think...
                                // if there is more then 1 friend arround, and we have TUs to FIRE, we try to come closer to the enemy
                                if( (friendsnear > 1) && (agVars.enemyUnits[currmodelnum].TUs > TUCOSTFIRE) ) {
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: enemies far, but friends arround. I'll try to come closer to my closest enemy\n");
#endif
                                    agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONCOMECLOSER;
                                    return;
                                } else {// if there are no friends arround, or we do not have enough TUs to fire
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: enemies far, no friends arround, i'll stay here\n");
#endif
                                    agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONDONOTHING;
                                    return;
                                }
                            }// enemies close enough but unseen
                        }// no foes near
                        else {   // there are foes near
                            // and there are friends arround
                            if(friendsnear > 0) {
                                // if we are OK on health, come closer and attack
                                if(agVars.enemyUnits[currmodelnum].health > AIHEALTHLOW) {
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: i have a lot of health, i'll come closer and attack\n");
#endif
                                    agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONCOMEANDFIRE;
                                    return;
                                } else {    // health low, run away
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: health low, running away\n");
#endif
                                    agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONRUNAWAY;
                                    return;
                                }
                            }// foes near, friends near
                            else {   //no friends near
                                // if there are more then 2 foes and no friends, run away
                                if(/*foesnear*/agVars.enemyUnits[currmodelnum].visibleEnemiesCount > 2) {
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: no friends arround, too many foes, running away\n");
#endif
                                    agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONRUNAWAY;
                                    return;
                                } else {   // less then 2 foes and no friends
                                    // if there is enough health, attack
                                    if(agVars.enemyUnits[currmodelnum].health > AIHEALTHLOW) {
#ifdef DEBUGACTIONGAME
                                        leafLog(LEAF_NO, "AG: AI: no friends, less then 2 foes, but health ok, attacking\n");
#endif
                                        agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONFIRENOW;
                                        return;
                                    } else {   // health problems, run away
#ifdef DEBUGACTIONGAME
                                        leafLog(LEAF_NO, "AG: AI: no friends, less then 2 foes, low health -> running away\n");
#endif
                                        agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONRUNAWAY;
                                        return;
                                    }
                                }// no friends and less then 2 foes
                            }// foes near, no friends near
                        }// foes near
                        break;  // LEAFAIACTIONINIT
                        
                    case LEAFAIACTIONFIRENOW:
                        // is enemy visible?
                        if(!agIsGloballyVisible((int)agVars.enemyUnits[currmodelnum].x, (int)agVars.enemyUnits[currmodelnum].z)) {
#ifdef DEBUGACTIONGAME
                            leafLog(LEAF_NO, "AG: AI: i see enemies, but i am invisible, i will do nothing\n");
#endif
                            agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONDONOTHING;
                            return;
                        }
                        // if we can, fastsit!
                        if((agVars.enemyUnits[currmodelnum].skills & skills[SKILLFASTSIT]) && (!agVars.enemyUnits[currmodelnum].isSitting))
                        {
#ifdef DEBUGACTIONGAME
                            leafLog(LEAF_NO, "AG: AI: Can sit!\n");
#endif
                            agVars.enemyUnits[currmodelnum].isSitting = LEAF_YES;
                            md2ChangeAnimation(&agVars.enemyUnits[currmodelnum].model, ANIMSITDN, agVars.currTime);
                            return;
                        }
                        // before firing, we need to make sure there are visible foes, and shoot them only!
                        if(closestvisiblefoe >= 0) {
                            agAIFire(&agVars.enemyUnits[currmodelnum], &agVars.playerUnits[closestvisiblefoe]);
                        } else {
                            // there are no visible foes, what do we do????
                            // we're either already came closer, or we cannot...
                            // so we pass
                            agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONDONOTHING;
                        }
                        break;// LEAFAIACTIONEFIRENOW
                        
                    case LEAFAIBERSERK:
                        // this happens when there's only one enemyunit left on the map.
                        // right now it will act as if it recieved LEAFAIACTIONCOMEANDFIRE
                        agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONCOMEANDFIRE;
                        break; // LEAFAIBERSERK
                        
                    case LEAFAIACTIONCOMEANDFIRE:
                        agAIComeCloser(&agVars.enemyUnits[currmodelnum], (int)agVars.playerUnits[closestfoe].x, (int)agVars.playerUnits[closestfoe].z, TUCOSTFIRE, LEAFAIACTIONINIT);
                        agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONFIRENOW;
                        break;// LEAFAIACTIONCOMEANDFIRE
                        
                    case LEAFAIACTIONCOMECLOSER:
                        agVars.enemyUnits[currmodelnum].aiState = agAIComeCloser(&agVars.enemyUnits[currmodelnum], (int)agVars.playerUnits[closestfoe].x, (int)agVars.playerUnits[closestfoe].z, TUCOSTFIRE, LEAFAIACTIONINIT);
                        break;// LEAFAIACTIONCOMECLOSER
                        
                    case LEAFAIACTIONRUNAWAY:
                        // same as walk closer, but walking closer to nearest friend
                        // if there are any TUs, we run!
                        if(agVars.enemyUnits[currmodelnum].TUs > 0) {
                            // will run away if there is a friend to run to. If left alone, fight!
                            if(closestfriend < 0) {
#ifdef DEBUGACTIONGAME
                                leafLog(LEAF_NO, "AG: AI: Want to run away, but no friends near.\n");
#endif
                                if(agVars.enemyUnits[currmodelnum].TUs >= TUCOSTFIRE) {
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: No friends to run to but have TUs to fire!\n");
#endif
                                    agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONFIRENOW;
                                    return;
                                }// have TUs to fire
                                else {
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: No friends to run to and no TUs to fire.. Staying here.\n");
#endif
                                    agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONDONOTHING;
                                    return;
                                }// no TUs to fire
                            }// there are no friends close
                            agVars.enemyUnits[currmodelnum].aiState = agAIComeCloser(&agVars.enemyUnits[currmodelnum], (int)agVars.enemyUnits[closestfriend].x, (int)agVars.enemyUnits[closestfriend].z, TUCOSTAUTOFIRE, LEAFAIACTIONFIRENOW);
                        }
                        break;// LEAFAIACTIONRUNAWAY
                        
                    case LEAFAIACTIONDONOTHING:
                        // if unit has FASTSIT skill, we sit it down
                        if(agVars.enemyUnits[currmodelnum].health > 0) {
                            if((agVars.enemyUnits[currmodelnum].skills & skills[SKILLFASTSIT]) && (!agVars.enemyUnits[currmodelnum].isSitting) )
                            {
                                agVars.enemyUnits[currmodelnum].isSitting = LEAF_YES;
                                md2ChangeAnimation(&agVars.enemyUnits[currmodelnum].model, ANIMSITDN, agVars.currTime);
                                break;
                            } else {
                                // now it has nothing to do and sits, if it has fastsit
                                // we check for any RELEASED hostages we can kill and shoot them, if we can.
                                if(agVars.enemyUnits[currmodelnum].TUs >= TUCOSTFIRE) {
                                    for(int k=0; k<agVars.hostageUnitsCount; k++) {
                                        if( (agVars.hostageUnits[k].health > 0) && (agVars.hostageUnits[k].aiState == LEAFAIHOSTAGERELEASED) )
                                        {
                                            if(agIsVisibleFor(&agVars.enemyUnits[currmodelnum], &agVars.hostageUnits[k])) {
#ifdef DEBUGACTIONGAME
                                                leafLog(LEAF_NO, "AG: AI: Nothing to do, but i see a released hostage to kill.\n");
#endif
                                                agAIFire(&agVars.enemyUnits[currmodelnum], &agVars.hostageUnits[k]);
                                                agVars.enemyUnits[currmodelnum].TUs -= TUCOSTFIRE;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        // Then we are done
                        agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONENDMOVE;
                        break;
                        
//************************************************************************************
                        
                    case LEAFAIACTIONCHECKMESSAGES: {
#ifdef DEBUGACTIONGAME
                        leafLog(LEAF_NO, "AG: AI: in LEAFAIACTIONCHECKMESSAGES\n");
#endif
                        switch(agVars.enemyUnits[currmodelnum].aiInbox) {
                            case AIMESSAGECALLTOARMS:
                                // FIRST OF ALL!!! clear message
                                agVars.enemyUnits[currmodelnum].aiInbox = AIMESSAGENOMESSAGES;
                                // go to sender
                                if(agVars.enemyUnits[currmodelnum].TUs > 0) {
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: recieved CALLTOARMS from %i, going to: %i:%i to %i:%i\n", agVars.enemyUnits[currmodelnum].aiSender, (int)agVars.enemyUnits[currmodelnum].x, (int)agVars.enemyUnits[currmodelnum].z, (int)agVars.enemyUnits[agVars.enemyUnits[currmodelnum].aiSender].x, (int)agVars.enemyUnits[agVars.enemyUnits[currmodelnum].aiSender].z);
#endif
                                    // if sender is close enough, ignore
                                    if(agGetDistanceSQ(&agVars.enemyUnits[agVars.enemyUnits[currmodelnum].aiSender], &agVars.enemyUnits[currmodelnum]) <= 9)
                                    {
#ifdef DEBUGACTIONGAME
                                        leafLog(LEAF_NO, "AG: AI: I am too close to come closer to sender. On my own now.\n");
#endif
                                        // reinit AI.
                                        agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONINIT;
                                        return;
                                    }
                                    agVars.enemyUnits[currmodelnum].aiState = agAIComeCloser(&agVars.enemyUnits[currmodelnum], (int)agVars.enemyUnits[agVars.enemyUnits[currmodelnum].aiSender].x, (int)agVars.enemyUnits[agVars.enemyUnits[currmodelnum].aiSender].z, TUCOSTFIRE, LEAFAIACTIONINIT);
                                }// enough TUs to come closer
                                else {   // no enough TUs to come closer
#ifdef DEBUGACTIONGAME
                                    leafLog(LEAF_NO, "AG: AI: No TUs to come closer. I'll do nothing\n");
#endif
                                    agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONDONOTHING;
                                    return;
                                }
                                break;// AIMESSAGECALLTOARMS
                                
                            default:
#ifdef DEBUGACTIONGAME
                                leafLog(LEAF_NO, "AG: AI: Unknown message!!!\n");
#endif
                                agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONINIT;
                                break;
                        }
                    } break;// LEAFAIACTIONCHECKMESSAGES
                        
//************************************************************************************
                        
                    default:
#ifdef DEBUGACTIONGAME
                        leafLog(LEAF_NO, "AG: AI: Unknow AI state in EnemyLogic for enemy %i\n", currmodelnum);
#endif
                        agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONENDMOVE;
                        return;
                        break;
                }// switch currmodel->AIstate
            }// alive and has TUs
            else {// dead and/or has no TUs
                agVars.enemyUnits[currmodelnum].aiState = LEAFAIACTIONENDMOVE;
                return;
            }// dead and/or has no TUs
            break;  // GLOBAL AI ACTION
            
        case LEAFAIGLOBALCOMPLETED: {// finish turn
            // but first...
            // we will check if there are units who have a lot of unused TUs
            // if there are other units not far who see enemies, we move those units to support them
            int noFurtherMoves = LEAF_YES;
            for(int i=0; i<agVars.enemyUnitsCount; i++) {
                if(agVars.enemyUnits[i].health < AGAIHEALTHTOHELP) {
                    continue;
                }
                if(enemy[agVars.enemyUnits[i].type].helpDistanceSQ < SMALLNUMBER) { // 0.0f for units who don't run to help
                    continue;
                }
                if(agVars.enemyUnits[i].TUs > AGAITUSTOMOVETOHELP) {
                    // enough TUs. Let's see if there are units around who see eneies
                    float closestUnitSQ = GAMEVIEWDISTSQARED*2;// too far to start with
                    int helpeeIndex = -1;
                    for(int k=0; k<agVars.enemyUnitsCount; k++) {
                        // if self, skip
                        if(i==k) {
                            continue;
                        }
                        // is the new unit seeing enemy
                        if(agVars.enemyUnits[k].visibleEnemiesCount > 0) {
                            // is unit close enough? is unit far enough? (Maybe we're already there)
                            float newDistance = agGetDistanceSQ(&agVars.enemyUnits[i], &agVars.enemyUnits[k]);
                            if((newDistance < enemy[agVars.enemyUnits[i].type].helpDistanceSQ) && (newDistance > 1.0f-SMALLNUMBER)) {
                                // do we already know a unit who needs help and is closer
                                if( (helpeeIndex == -1) || (closestUnitSQ > newDistance) ) {
                                    helpeeIndex = k;
                                    closestUnitSQ = newDistance;
                                }// found new helpee
                            }// found close helpee
                        }// found helpee
                    } // for all helpees
                    if(helpeeIndex > -1) {
#ifdef DEBUGACTIONGAME
                        leafLog(LEAF_NO, "AG: AI: enemy %i is rushing to help enemy %i\n", i, helpeeIndex);
                        agPrintToInfoboard(getLocalizedString("DE6"));
#endif
                        agVars.enemyUnits[i].aiState = agAIComeCloser(&agVars.enemyUnits[i], (int)agVars.enemyUnits[helpeeIndex].x, (int)agVars.enemyUnits[helpeeIndex].z, TUCOSTAUTOFIRE, LEAFAIACTIONINIT);
                        // is there a path at all?
                        if(agVars.enemyUnits[i].aiState != LEAFAIACTIONDONOTHING) {
                            noFurtherMoves = LEAF_NO;
                            agVars.aiState = LEAFAIGLOBALACTION;
                            break;
                        }
                    }
                }// if helper has enough TUs
            }// for all helpers
            if(noFurtherMoves) {
                agChangeTurn(TURNHOSTAGES);
            }
        } break;  // GLOBAL AI COMPLETED
            
        default:
            break;
    }// switch global AIstate
}// agProcessAI

Код просто поэма! =D
Ну или как минимум дневник с терзаниями и сомнениями одного кровожадного террориста
Прекрасный захватывающий рассказ.
Очень крутая трилогия вышла. Даже жаль что нет смарта от эпл, с радостью бы поиграл. Спасибо за рассказ.)
На андроид, к сожалению, пороха не хватило. Хотя порт должен быть довольно простым, по идее, если делать через NDK
Спасибо за текст. Замечательно написано :-)

Имхо основное правило начинающих гейм девелоперов — не писать свой движок. Их же полно готовых уже, а так пару лет вы потеряли на это. И вообще, современные игры это на 30% красивый контент, на 30% раскрутка и купленный трафик, и лишь на 30% код. Ваш результат в плане прибыли тут увы, закономерен.

Но зато получили интересный опыт, а это классно. И респект за терпение — делать проект 3 года сможет не каждый.
Мне было интересно писать именно движок. Я по натуре не игродел, наверное )
Но, думаю, вы правы.
Перед тем как начать, стоило хотя бы перечитать статьи на gamedev.ru.

Я тоже делал раз простую игру для iOS, в итоге понял что бесполезно — сейчас gamedev это миллионный рынок где рулит бабло, а бюджеты современных игр уже, бывает, превосходят бюджеты художественных фильмов. Эпоха когда можно было написать «Тетрис» или «Поле чудес» и стать знаменитым, увы прошла.

Про тот же XCom, в плане понимания масштаба и человеко-часов:

www.eurogamer.net/articles/2012-03-06-firaxis-xcom-is-a-very-very-big-budget-game
«We're 50, 60 guys, I don't know exactly,» he said. «We've been working on it for three-and-a-half, four years. It's a big, big game. It's definitely as big as any game we've ever made at Firaxis. It's huge. It's a bit like piloting a big old boat.»

Вообще, gamedev это своя специфика разработки — сжатые сроки и частые переработки, меняющиеся требования, зажранные юзеры, невысокие зарплаты, и эта ниша для фанатов в общем-то. Я год проработал, в итоге понял что это не мое от слова «совсем».

Удачи в Париже, и в изучении французского. Был там этим летом :)
А почему вы решили делать свой движок, а не использовать тот же Unity, например?
Юнити в то время был ещё не торт.
Ну и, как я написал выше, мне было просто интересно писать движок.
Как-то грустно, похоронили со всеми почестями?
само собой. 2 недели гиктаймс статьями мучал.
Sign up to leave a comment.

Articles