05 апреля 2010

Пример скрипта...

Решил чуточку рассказать про скрипты в "игреХ"...
в первом тексте я коснулся технического аспекта использования скриптов. сейчас же напишу о том, как в итоге скриптовые файлы-уровни представлены у меня.
Вот пример первого уровня:


файл 1:

function Create()
local tmp;
    Play_MusicSample('track1');
    Set_CameraPosition(-5, 0);
    Set_CameraViewArea(20, 19.5);
    Set_BackMaterial('back_1');

    bStatic = AddStaticWall(-11.13, 3.39, -3.80);
    AddStaticWall(-11.52, -0.99, 0.91);
    AddStaticWall(-7.48, 0.54, -0.65);

    AddStaticWall(-4.50, -1.63, 0.92);
    AddStaticWall(-2.95, -5.68, -0.65);
    AddStaticWall(-6.56, -10.48, -0.65);
    AddStaticWall(-10.63, -12.06, 0.93);
    AddStaticWall(-15.59, -2.50, -0.66);
   
    AddStaticWall(-2.38, 7.37, -0.66);
    AddStaticWall(1.95, 6.90, 0.90);
    AddStaticWall(6.66, 3.19, 0.90);
    tmp = AddStaticWall(8.10, -0.91, -0.67);

    bDoor1 = Add_WoodWall(0, -14.58, -7.18, 0.93, 2);
    local ax, ay = GetSinCos(0.93 + math.pi / 2);   
    jDoor1 = Add_JointSkid(0, bDoor1, bStatic, -12.51, -8.48, ay, ax);
    bSpring = Add_SpringSprite(grBackRoot, -10.14, -10.55, 0.93, 1, 3);

    bDoor2 = Add_WoodWall(0, 2.64, -2.44, -1.48, 3);
    jDoor2 = Add_JointNail(grMediumRoot, tmp, bDoor2, 5.79, -2.74);
    Set_JointLimits(jDoor2, 1, 0, 0);
    Set_PhysParams(bDoor2, 0.3, 0, 0.4);   
    bLock = Add_Lock(0, -1.10, -2.32, 3.60, 1);
    jLock = Add_JointNail(grMediumRoot, bLock, bDoor2, -0.67, -2.74);
    Set_JointLimits(jLock, 1, -0.19, 0.5);
    bKey = Add_Key(0, -9.57, 1.63, 0.43, 2);
    Set_PhysParams(bKey,  1, 0, 0.4);
    Set_PhysParams(bLock, 1, 0, 0.4);
    bLockIcon = Add_FlyAwayLockIcon(grFrontRoot, -1.17, -2.53, 0, 1);
    Do_Command(bLockIcon, 23, 1, 0);
    Do_Command(bLockIcon, 22, 1, 2);
   
    bSymbol = Add_Symbol(0, 0.15, 3.26, 0, 1);
    bBWSymbol = Add_GraphSprite(grBackRoot, -6.57, -6.15, 0.2, 1.5, 1.5, 'symbol_bw_1');
    bHint4 = Add_AlphaHint(0, -5.62, 8.01, 0, 3, 3,  'hint_4')
    Do_Command(bHint4, 23, 1, 0);
   
    local ax, ay = GetSinCos(-0.67 + math.pi);   
    bBarDoor = Add_BarDoor(0, -7.24, 5.43, -0.67, 1);
    jBarDoor = Add_JointSkid(0, bBarDoor, bStatic, -4.86, 3.57, ay, ax);
    SetStaticMass(bBarDoor);
   
    bGear1, jGear1 = AddGearAndNail(-15.71, 11.87, 3.27, 4, 1);
    bGear2, jGear2 = AddGearAndNail(-15.2, 8.0, 1.28,  2, 1);
    bGear3, jGear3 = AddGearAndNail(-21.56, 5.83, 6.85,  1, 1);
    Set_JointMotors(jGear3, 1, 400, 400);
    Add_JointSimpleGear(bGear1, bGear2, jGear1, jGear2, 9/11);
    Add_LevelMaterial('chgear_3.png', 'level_chalk_1');
    bChalkGear4 = Add_GraphSprite(grBackRoot, -17.96, 5.74, -10.90, 3.5, 3.5, 'level_chalk_1');
    Add_JointShank(0, bGear1, bBarDoor, -13.60, 10.24, -9.67, 7.34);

    bGear4 = Add_MetallGear(0, -6.32, -4.57, -7.88, 3);
    bSwitch, jSwitch = AddSimpleSwitchArm(-14.19, -12.57, 2.52, 1, bStatic);
   
    jGear4 = 0;
    unlocked = 0;
   
    Add_LevelMaterial('ivy_2.png', 'level_ivy_1');
    Add_LevelMaterial('ivy_4.png', 'level_ivy_2');
    Add_LevelMaterial('ivy_9.png', 'level_ivy_3');
    Add_GraphSprite(grFrontRoot, 9.32, 0.41, 1.7, 3, 1.5, 'level_ivy_1');
    Add_GraphSprite(grFrontRoot, -0.97, 9.62, -1.7, 3, 1.5, 'level_ivy_2');   
    Add_GraphSprite(grFrontRoot, -8.93, -13.86, 1.7, 3, 1.5, 'level_ivy_2');   
    Add_GraphSprite(grFrontRoot, -1.79, 8.27, 1.0, 3, 1.5, 'level_ivy_2');   
    Add_GraphSprite(grFrontRoot, -6.71, 0.62, 1.2, 4, 2, 'level_ivy_3');
end;

function Destroy()
    Clear_LevelMaterial('level_ivy_1');
    Clear_LevelMaterial('level_ivy_2');
    Clear_LevelMaterial('level_ivy_3');
    Clear_LevelMaterial('lvl_hint_1');
    Clear_LevelMaterial('lvl_hint_2');
    Clear_LevelMaterial('level_chalk_1');
    Clear_LevelMaterial('level_led_2');
end;

файл 2:

function AttachScripts()
    Attach_ScriptToObject(bDoor1,     'OnMove',         'bDoor1OnMove');
    Attach_ScriptToObject(bKey,       'OnAddContact',   'bKeyOnAddContact');
    Attach_ScriptToObject(bSwitch,    'OnRotate',       'bSwitchOnRotate');
    Attach_ScriptToObject(bGear4,     'OnMove',         'bGear4OnMove');
    Attach_ScriptToObject(bSymbol,    'OnMove',         'bSymbolOnMove');   
end;

function bDoor1OnMove(PosX, PosY)
    local x, y, angle =    Get_ScrObjPos(bDoor1);
    local ax, ay = -10.92 - x, -9.98 - y;
    l = math.sqrt(ax*ax + ay*ay) / 1.8;
    local ms, mc = GetSinCos(angle - math.pi / 2);
    x = x + ax / 2 + 3 * mc;
    y = y + ay / 2 + 3 * ms;
    Do_Command(bSpring, 1, 2, 0.7, l);
    Set_ScrObjPos(bSpring, x, y, angle);
end;

function bKeyOnAddContact(Body1, Body2)
    if (Body2 == bLock) and (unlocked == 0)then
        unlocked = 1;
        Set_JointLimits(jDoor2, 1, 0, 2.39);
        Set_JointMotors(jDoor2, 1, 300, 300)
        Destroy_Joint(jLock);
        Play_SoundSample('door_unlock');
        Do_Command(bLockIcon, 23, 1, 1);
    end   
end;

function bGear4OnMove(PosX, PosY)
    local x, y, angle = Get_ScrObjPos(bGear4);
    local x, y, tmp = Get_ScrObjPos(bChalkGear4);
    local l = GetVectorNorm(x, y, PosX, PosY);
    if (l < 0.25) and (jGear4 == 0) then
        Set_ScrObjPos(bGear4, x, y, angle);
        jGear4 = Add_JointNail(0, bStatic, bGear4, x, y);
        jGear43 = Add_JointSimpleGear(bGear4, bGear3, jGear4, jGear3,  8/10);
        jGear42 = Add_JointSimpleGear(bGear4, bGear2, jGear4, jGear2,  9/10);
        Set_PhysParams(bBarDoor, 0.3, 0, 0.4);
    end;
end;

function bSwitchOnRotate(Rotation)
    local angle = Get_JointAngle(jSwitch);
    if (angle <= 0.1) then
        Set_JointMotors(jSwitch, 1, -150, 150);
        Set_JointMotors(jDoor1, 1, -200, 200);
    else
        Set_JointMotors(jSwitch, 1, 150, 150);
        Set_JointMotors(jDoor1, 1, 200, 200);
    end   
end;

function bSymbolOnMove(PosX, PosY)
    local x1, y1, ang = Get_ScrObjPos(bBWSymbol);
    local l = GetVectorNorm(x1, y1, PosX, PosY);
    if (l < 0.3) then
      Do_Command(bHint4, 23, 1, 1);
    end;
end;

Вот и все... как происходит все дело? зачитываются оба этих файла и еще несколько вспомогательных, с уникальными объектами уровня. после этого выполняется скриптовая функция Create(), затем AttachScripts(). при завершении уровня вызывается Destroy().
что конкретно внутри инициализационной ф-ии Create? с этим труднее... по сути, создаются объекты, сочленения, расставляются хинты и графическое наполнение; иногда определяются  или меняются дефолтные параметры объектов. также присутствуют вызовы Add_LevelMaterial(), которые загружают новый материал в библиотеку материалов для дальнейшего использования. зачем? зачастую на уровне бывают объекты, которые ни в одном другом уровне не встретятся. зачем расточительно использовать память? загружаем при старте, убиваем при выходе. вот и вся магия. в самом начале Create-функции можно видеть установку позиции камеры, размеры области уровня, фоновое изображение и задание музыкальной композиции на этот уровень.
Функция AttachScripts() служит для базового присвоения функций-обработчиков событий телам. например, Attach_ScriptToObject(bDoor1, 'OnMove', 'bDoor1OnMove'); указывает на то, что теперь, при движении объекта bDoor1, должна вызываться функция bDoor1OnMove() из скриптового файла. По сути, все Attach_ScriptToObject() можно было запихнуть в Create-функцию. Это так, но мне удобнее события объектов и  их создание держать в разных файлах. Дело вкуса на самом деле.
При смене уровня, вызовется Destroy(), который почистит библиотеку материалов от ненужных текстур.
Именно в этом примере нет функции DoProgress(), которую я иногда использую для idle-обработчиков. конечно, наверняка можно было обойтись и без них, но я пока не стал. к примеру, была задача прикладывать силу к телу в зависимости от текущего кадра анимации некоторого объекта. я так и не придумал ничего умнее, чем воспользоваться DoProgress()-функцией...
Кстати, можно заметить, что у некоторых событий есть входные аргументы - это моя гордость. Казалось бы - передал пару параметров из хост-программы и все, чего проще? Но дело в том, что нельзя вызывать скрипт-событие из хост-программы в момент возникновения самого события. Потому как зачастую из скрипта начинают меняться параметры физических объектов. А само событие возникает во время физической симуляции, потому параметры всех тел сам Box2d лочит на время симуляции. в итоге пришлось накручивать скрипт-менеджер со своей очередью событий. таким образом, при возникновении контакта (или другого события) в Box2d, внутри хост-программы происходит запихивание названия функции скрипта в конец очереди событий в скрипт-менеджере. В конце симуляции раскручивается вся очередь и последовательно выполняются все события. В общем, я не поленился и храню теперь в очереди не только название функции, но и набор параметров, которые затем передаются в качестве аргументов в скрипт-функцию. Кстати, после этого всего заодно выполняется и DoProgress(), мало ли...
В общем, как-то так... сильно расписывать каждую функцию смысла нет, но общая направленность вроде ясна :)

Сообщения, схожие по тематике:

5 коммент.:

  1. А разве не должны функции 1 файла и первая половина 2 файла находиться в файле редактора карт?

    ОтветитьУдалить
  2. в каком смысле? эти два файла - это целостный уровень и функции-события самого уровня. по сути ничего лишнего. часть текста этих файлов генерирует редактор карт и сопутствующие тулзы сами, а дальше просто ctrl+v в notepad++. то есть в редакторе расставил объекты, создал сочленения, предела и моторы - а потом из редактора все это переношу в скрипт-файл.

    просто некоторые вещи требуют внимания - имена объектов, вынос отдельных функций и определение луа-таблиц. поэтому на полную автоматическую генерацию скрипт-файлов я перейти не сумел.

    ОтветитьУдалить
  3. Подумай над плэйлистом))
    типо функции Add_MusicSample()
    ведь прохождение уровня может затянуться за 10 минут, а я не думаю что у тебя трэк будет под все 10 минут и не всегда будет возможность и желание а так же смысл ставить цикличную мелодию

    в общем прикольно. но текст текстом, а картинка картинкой)))

    ОтветитьУдалить
  4. Lampogolovii, не обращай внимания, ты уже ответил на мой вопрос))) А когда тизер будет?

    ОтветитьУдалить
  5. Aero, в принципе один уровень проходится довольно быстро (надеюсь, что самый сложный пройдется в среднем минут за 7-8). но хочется при этом, чтобы музыка звучала "в тему". то есть, к примеру, использовать 4-5 треков под разные нужды - аркадный уровень, сложно-головоломистый, эмбиент для начальных уровней и трек под меню и затяжные (почти филлерские) уровни.

    gltrinix, ок, раз ответил - это позитив;) насчет тизера помню - на этой неделе попробую сделать, если будут время и возможность... просто я не шибко вижу в нем нужды - время разработки он только увеличит, а сейчас разрывать мэйнстрим создания уровней пока не хочется. но я помню - так что постараюсь!;)

    ОтветитьУдалить