Insomnia

Chessformer

Telepath Tactics

SomethingXD

Инди-подборка… уже двадцатая?!

Избранное пользователя

Тебе наверное, нужно там вместо self написать object_index (или self.object_index, без разницы) - по сути это айди ресурса-объекта. Когда ты пишешь в коде название ресурса-объекта - то это тоже айди, т.е. номер ассета по порядку в дереве ассетов справа. Если перенесёшь ресурс то этот нормер поменяется. Это внутренняя кухня для понимания. Иногда попутаешь в функции instance_create порядок аргументов и утебя херня создастся.

Так что вот смотри:
object_index - это айди ресурса. Его можно передавать в instance_create функции
object_get_name(object_index) - строковое название ресурса, его ты передать в эти функции не можешь. Но есть функция asset_get_index, которая по этой строке найдёт id ресурса.

Если ты хочешь это использовать для сохранения состояния игры то лучше получать строку. Если в рамках одной игровой сессии то проще будет object_index хранить.

Так, я посмотрел.

MAIN -> Create:

global.YesObject=noone; // Yes/No Target Object for Dialogues
global.Ask="";global.YesAct=""; global.YesMode=""; //For "Yes/No" Menu

А для чего нам глобально эти три переменные, если у нас уже есть YesObject, в котором их можно хранить? У тебя тут место для потенциальной несостыковки скриптов в будущем - может выйти так что global.YesObject уже noone, но global.YesAct всё ещё хранит значение от предыдущего, и запаришься искать ошибки.

//Auto Resize Rooms View
for(var i=1; i<=room_last; i++) {
  if(room_exists(i)) {
    room_set_view(i,0,true,0,0,screenW,screenH,0,0,screenW,screenH,0,0,0,0,-1);
    room_set_view_enabled(i,true);
  }
}

Не-не, ни в коем случае не i=1, должно быть i=room_first, ну а если кроме первой (видимо ты полагал что нумерация начинается с нуля, и тогда 1 это вторая комната), то делай i=room_next(room_first).

if (room_next(room) != -1)

Только room_exists(room_next(room)), все эти цифры типа i=1 для комнат и -1 вместо индексов ресурсов - зло.

MAIN -> Step:

menus("open","o_menuPause");

Ты передаёшь тут название объекта, но:

1) Делаешь это строкой - тебе нужен asset_get_index("o_menu_pause") чтобы получить индекс объекта из строки;

2) На самом деле тебе достаточно просто писать не в кавычках, и тогда asset_get_index() уже не нужен;

3) Скрипт menus ничего потом не делает с этим аргументом кроме сравнения с пустой строкой, ты вроде как сделал пустые слоты для чего-то в будущем, но непонятно для чего.

То же самое на строке 15 в скрипте Talk. Идём далее:

Draw9x9

Монструозный скрипт, думаю ты запарился его выверять пока написал. С другой стороны, ты выставил ориджин спрайта в центр и прописал поворот через градусы, так что тут даже не к чему придраться. Молодец!

o_FoeCore -> o_HeroGirl Collision

    var _ang = arctan2(other.y-y, other.x-x)+90;
    other.HurtSX = sin(_ang) * 10;
    other.HurtSY = -cos(_ang) * 10;

Собственно, это и есть lengthdir (вот это подстава, правда?):

    var _ang = point_direction(x,y,other.y,other.y)+90
    other.HurtSX = lengthdir_x(_ang, 10)
    other.HurtSY = lengthdir_x(_ang, 10)

Правильно организовал наследование o_coll, тут тоже молодец.

Скрипту drawGirl() не нужен аргумент - ты вызываешь его только из её объекта, да и само название говорит о том что он уместен только там. Если убрать из скобок self, а из тела скрипта var _self = argument0; и все "_self.", то работать код будет точно так же само.

Для того чтобы обращаться к переменным вызвавшего объекта, не обязательно писать ни self.dir, ни id.dir, ни пропускать это через скрипт и делать _self.dir, хотя все эти три способа тоже будут работать. Просто пишешь dir и скрипт, обладая контекстом вызова, сам достаёт эту переменную из вызвавшего инстанса.

o_BoxCore -> Create

isUse = false;
isUse2 = false;

Это ты делаешь два флага для того чтоб на второе использование давать другой результат? Не очень понятно для чего это предназначено.

В гм просто делается - window_set_fullscreen(true или false). А если set заменить на get то можно наоборот проверять в фуллскрине игра или нет.

MhP0KPm

self работает быстрее. Вообще не очень понимаю зачем её использовать, разве что как в примере, если ты переменные именуешь как дегенерат, мешая всё подряд. Если взять за правило глобалы именовать капсом, локальные строчными, а все переменые, объявленные через var начинать с почерка то и проблем нету. Можно какие-то другие правила выработать.

Я не использую self и не использую id для обращения к переменным объекта, так что мне кажется это вообще избыточный функционал.

Тот же пример но без self

var _val = 100;
with (instance_create_layer(x, y, "Instances", obj_Fire))
  {
  val = _val;
  }

Тут прикол в том, что всё что объявлено через var в таких конструкциях доступно не зависимо от скоупа и имеет более высокий приоритет. Именуйте переменные правильно и не ебеись с self.

Я примерно так паузу делаю:

//При переходе в паузу
room_persitent=true;
PAUSE_SPRITE = sprite_create_from_surface(application_surface, 0, 0, surface_get_wudth(), surface_get_height(), false, false, 0, 0);
room_goto(pause_room)

//В паузе рисуем спрайт
draw_sprite(PAUSE_SPRITE,0,0,0);

//При возврате из паузы
sprite_delete(PAUSE_SPRITE);
room_persistent=false;

Почти то же самое но без перехода в другую комнату можно сделать по официальному гайду:
https://www.yoyogames.com/blog/551/coffee-break-tutorials-pausing-your-game-gml

И я не пойму, чем этот способ плох и чем он не подходит? Он не отражает запаузенность или что?

Если прям очень нужно чтобы спрайты были анимироваными, то с некоторыми оговорками можно попробовать что-то такое:

//в Draw событии объекта-паузы

instance_activate_all()
with all {
image_index+=image_speed
if id!=other.id then event_perform(ev_draw,0);
}
instance_deactivate_all()

Оговорки из серии "простое рисование", "без постобработок","не использовать image_index для условий" (иначе взрывы будут плохо отрабатывать).

На дворе 2020 год, почему до сих пор нет нормального 2D-движка, а есть только конструктор с кучей ограничений и фреймворки, где надо все делать руками?

Phaser, Flixel - вот тебе фреймворки, там где ты можешь всё что хочешь сделать. Только ручками допиши =)

Про ограничения я не очень понял. В гамаке можно сделать так же как в unity, но вручную, обсчитывая для всех объектов свои переменные и заведя свои скрипты для их обсчёта. Делается довольно просто через объект-прототип, в который закладывается вся эта функциональность.

Такие штуки делаются путём обращения через with, простым обращением, как к переменной, этого не добиться. Может в гмс 2.3 что-то поменялось, но пока так.

with(other)event_user(0)

Я тут для иллюстрации набросал схему. Сложно читается, но думаю суть ясна. Не важно сколько уровней наследования ты используешь, на каждом из них ты экономишь силы при расширении функционала конкретных объектов, за счёт изменения абстрактных объектов.

0qfZGxF

1) Например я хочу чтобы моя пуля сталкивался с 4-мя типами объектов: Стены, Другие Пули, Враги, Герой.
Должен ли я писать 4 функции для этого или хватит просто им проставить ну например свойство solid?!

Флаг solid вообще не для того - всё что ты обозначаешь как solid, будет выталкивать из себя все столкнувшиеся с ним объекты автоматически, причём это выталкивание может работать не так как тебе нужно, и настроить его нельзя. По большому счёту это вообще лишний функционал, тянущийся из старых Гамаков 15-летней давности чтобы не нарушать обратную совместимость.

По поводу твоего случая - ну, если бы я рассуждал таким образом, то я бы писал 1000 разных веток столкновений для 25 видов оружия и 40 видов врагов в моём проекте Void Source, но всё делается намного проще.

Если твои пули не летят с гигантской скоростью, то коллизии с ними (и чем угодно другим) легко можно поручить событию Collision (как ни странно), поэтому код проверки коллизии писать вообще не понадобится, сразу будешь писать действия которые при этом надо совершить.

Далее - тебе нужна абстракция, то есть некий объект, "то с чем может столкнуться пуля", а чтоб это было валидным названием объекта, назовём его o_shootable, дословно "то, во что можно стрелять". Теперь мы берём эти твои 4 типа объектов: "Стены, Другие Пули, Враги, Герой", и каждому из них назначаем объектом-родителем этот самый o_shootable, вот здесь:

ashyaSy

Теперь идём в объект-пулю, создаём там событие Collision с o_shootable и пишем туда код, ну скажем:

instance_destroy()

with other
    instance_destroy()

Теперь, с кем бы ни столкнулась пуля, она уничтожится сама, а потом уничтожит тот объект с которым столкнулась. Естественно, можно выполнять любой код как с пулей, так и с другим объектом, типа - отнять щит вместо хитпоинтов, вообще говоря проверить меньше ли хитпоинты нуля, прежде чем уничтожать объект, снизить скорость пули вместо уничтожения и летать дальше, и так далее.

Тут может стать вопрос - что если видов стен и врагов много, всем наследовать o_shootable? Нет! Тебе нужен объект абстрактного врага, объект абстрактной стены, и так далее. Они наследуют o_shootable, а каждый конкретный враг наследует o_enemy, тогда как каждая конкретная стена - o_wall. Нафига, спросишь ты. Для того чтобы вместо 40x25 коллизий у тебя только в абстрактном выстреле была только одна коллизия с абстрактным врагом, и все конкретные выстрелы, враги, стены, и так далее - автоматически наследовали это поведение.

2) Ещё я не уверен, можно ли как-то послать сигнал объекту, когда столкнулся с ним, потому-что кажется что внутри объектов в GMS нет классических функций, как в юнити происходит методом SendMessage или GetComponent<Class>.MyFunction();,
мне нужно послать сигнал урона, к объекту с которым столкнулась пуля, типа MinusHP(5);

У объектов в GMS есть пользовательские события, делаешь так:

mo36vqh

Потом в событии коллизии пули с o_shootable вместо того что я выше написал, делаешь так:

with other
    event_user(0)

И у объекта происходит обработка повреждений - такая, какую ты ему прописал в событие User Event 0, например:

hp-=1
if hp<=0
    instance_destroy()

Учитывая наследование, которое я описал выше, писать это нужно в абстрактные объекты, а не в конкретные, чтоб не копипастить всё по 200600 раз.

Альтернативный вариант:

with other
    receive_damage()

И создаёшь скрипт receive_damage() в котором пишешь то же самое, что и в предпоследнем куске кода - отнять хитпоинт, если закончились - исчезнуть. Ведь скрипты в ГМе - это и есть функции (примечание - только до версий раньше ГМС2.3 - начиная с 2.3 в одном скрипте может быть даже несколько функций).

inst.HurtSX = hspeed; // переменная скорости урона X
inst.HurtSY = vspeed; // переменная скорости урона Y

А это, кстати, для чего тебе передавать нужно? Или точнее - что это, скорость урона в координатах...???

смотри диспетчер... а так фиг знает...

так же чтобы определять с кем столкновение, можно использовать "кнопочную" коллизию с идентификаторами self.inst (obj name something other), other.

А, ты там потом ещё scale домножаешь на что-то. Ну тогда if (Mathf.Sign(transform.localScale.x) != direction).

Перемудрил ты с порядком отрисовки, используй SortingGroup. Да и с направлением движения переусложняешь, пожалуй.

BKW57pc

Отрисовка постобработки fullHD =)
Не лагает

Ты можешь автоматизировать подгрузку внешних спрайтов во время выполнения игры, с помощью функции sprite_add(). Там достаточно указать количество кадров, а остальные аргументы просто пиши 0 (ну или такой origin который тебе нужен, это смотря по ТЗ/диздоку).

3) https://www.gmlscripts.com/script/explode

4) Собственно, эта же ссылка. Пример оттуда процитирую:

names = "Juliett,Victor,Mike,Charlie,Romeo,Oscar";
 
array = explode(",", names);
 
//  array[0] == "Juliett"
//  array[1] == "Victor"
//  array[2] == "Mike"
//  array[3] == "Charlie"
//  array[4] == "Romeo"
//  array[5] == "Oscar"

Это отличается от варианта с Included files тем, что конечный пользователь (геймер) поменять твои настройки кнопок не сможет, ну и ведь это системное меню. Но ты можешь сделать и так и так.

Самый простой? Я бы сказал что писать везде только locale[N], где N это индекс строки в глобальном массиве locale, который читается из внешнего INI-файла со всё тем же CSV внутри на каждый язык. То есть содержимое файла:

Russian="Старт";"Загрузить";"Выход"
English="Start";"Load";"Exit"

В коде пишешь locale[0] там где Старт, 1 там где Загрузить, 2 там где Выход.

У нас в Замке Невозврата так доходит до locale[250] где-то, и 30 языков в игре - всё работает.

Инициализация:

if file_exists(working_directory+"Locale.ini")
    ini_open(working_directory+"Locale.ini")
else
    show_message_async("\"Locale.ini\" not found, reinstall the game!")

if ini_key_exists("Texts", global.language)
    raw_texts=ini_read_string("Texts",global.language,"")

ini_close()

globalvar locale;

locale_list=explode_string_list("^",raw_texts)
for (i=0; i<ds_list_size(locale_list); i++)
    locale[i]=ds_list_find_value(locale_list, i)

Скрипт explode_string_list:

/*
**  Usage:
**      list = explode_string_list(sep,data);
**
**  Arguments:
**      sep         seperator character, string
**      data        array data, string
**
**  Returns:
**      list        ds_list of elements
**
**  Notes:
**      Converts a string of data with elements seperated
**      by a delimiter into a list of strings.
**
**  GMLscripts.com, then reworked by Xitilon
*/
//Imported 07.08.2014 to ConE in Game Maker 8.1.140 (r6892)
//Reimported 03.12.2015 to ConE2 in Game Maker 8.1.140 (r6892)
//Ported 24.08.2018 to ConE2 in Game Maker Studio 1.4.1804
{
    var list,sep,dat,len,ind,pos;
    list = ds_list_create();
    sep = argument0;
    dat = argument1 + sep;
    len = string_length(sep);
    ind = 0;
    repeat (string_count(sep,dat)) {
        pos = string_pos(sep,dat)-1;
        ds_list_add(list, string_copy(dat,1,pos));
        dat = string_delete(dat,1,pos+len);
        ind += 1;
    }
    return list;
}

Начало файла Locale.ini для примера формата:

[Texts]
English=PRESS\n (A)^CHOOSE THE CHARACTER^GUARDSMAN\nHUMAN^HUNTRESS\nLIZARDIAN^CARDINAL\nN'RGON^...
Русский=НАЖМИ\nСТАРТ^ВЫБЕРИ ПЕРСОНАЖА^ГВАРДЕЕЦ\nЧЕЛОВЕК^ОХОТНИЦА\nЯЩЕР^КАРДИНАЛ\nН'РГОН^...

Символ ^ я выбрал в качестве разделителя потому что в текстах мне понадобилась точка с запятой. Так что по факту это не CSV (Comma-Separated Values), а... Caret-Separated Values, тоже CSV. :yak:

Как ты понял, это та причина почему скрипт explode_string_list вызывается с параметром "^".

P. S. И да, это простой способ. Если ты хочешь сложный, то в коде придётся писать каждый раз не locale[число], а идентификатор по типу locale[? "title_menu_start_button_text"] и в файл локали придётся добавлять каждый идентификатор тоже. То есть будет:

[English]
title_menu_start_button_text="Start"
...
[Russian]
title_menu_start_button_text="Старт"

И так все тексты. Раздувает файл локали и обращение к локализованным строкам в коде сразу в 2+ раза.

Ты можешь сделать удобную инициализацию с помощью двухмерного массива, каждый элемент которого - ds_map.

Create:

menu_grid_width=4
menu_grid_height=2

item = ds_map_create()
item[? "text"] = "Start"
item[? "act"] = "close"
item[? "mode"] = ""
item[? "ask"] = 1
item[? "icon"] = sprButton
item[? "text2"] = ""

menu_grid[0,0]=item

item = ds_map_create()
item[? "text"] = "Load"
item[? "act"] = "close"
item[? "mode"] = ""
item[? "ask"] = 1
item[? "icon"] = sprButton
item[? "text2"] = ""

menu_grid[0,1]=item

//...

menu_grid[1,0]=item
menu_grid[1,1]=item

menu_grid[2,0]=item
menu_grid[2,1]=item

menu_grid[3,0]=item
menu_grid[3,1]=item

Draw:

for (i=0; i<menu_grid_width; i++)
for (j=0; j<menu_grid_height; j++)
{
    item=menu_grid[i,j]
    if sprite_exists(item[? "icon"])
        draw_sprite(item[? "icon"],0,x+i*80,y+j*60)
    
    draw_set_color(c_black)
    draw_text(x+i*80,y+j*60,item[? "text"])
    draw_text(x+i*80,y+j*60+16,item[? "act"])
    draw_text(x+i*80,y+j*60+32,item[? "ask"])
}

Если сильно хотеть, можно вообще вынести чтение всех меню из внешнего CSV-файла (считай, таблица как в Microsoft Excel - он кстати CSV поддерживает). Но это плохо тем что Эксель тебе не подскажет какие sprButton у тебя есть или нет в ресурсах.

Не найдёшь ничего. Щас в айти нужных программистов некому учить, а про геймдев вообще шаром покати.

Вот держи, с нуля исходник сделал, конкретно иллюстрирующий как это делается:

https://www.dropbox.com/s/8d77fjft6laxo9g/ParentMenus.gmz?dl=0

Конечно могут понабежать Хейзеры и сказать что нафиг свитч-кейзы, но тогда придётся каждый раз из объекта меню переключаться на скрипты задаваемые пунктам. Или распихивать их по юзер-ивентам как ДД. Сути наследования основного функционала меню это не меняет.

event_inherited() выполняет код родителя из такого же ивента. Если в объекте-потомке нет ивента который есть в объекте-родителе, то код из объекта-родителя выполняется автоматически (в моём исходнике это происходит с Draw конкретно).

растения ловушки; растения охотники плюющиеся ядом, колючками, огнем или плазмой; общающиеся растения, которые разбегаются при виде героя; растения огородившие себя от сорняков полосой уничтоженной зацементированной земли или забором из мертвых сородичей(каннибализм - залог здоровья).
Представим себе цивилизацию растений - их техника копирует их устройство и подчиняется их нуждам. Значит - нет поступательных механизмов как человеческое сердце, нет переноса тепла; зато есть - мощные капиллярные насосы; контроль над подземными реками при помощи корней; сбор солнечной энергии гигантскими листьями; плантации пасущихся насекомых - выполняющих роли окучивателей или технопарикмахеров, грузчиков-транспортировщиков, павербанков. Есть передача информации спорами - цвет, состав, голография пыльцой - а потому концепция информации тесно привязана к размножению. (при всей мощности цветочной цивилизации они ассоциируются у зрителя с прекрасным, эстетичным, потому аллюзия на цивилизацию хиппи. но могущественных)
А еще в небе парят огромные травоядные птицы, которых растения почитают за богов и от которых защищаются самым решительным способом. С одной из таких птиц и был перепутан корабль гг. А сам он считается птенцом, которого растения задумали перевоспитать ради выведения нового вида птиц - дружеского, ведь информация есть размножение, а размножение есть любовь(или подчиненного, ведь животные это низший ранг и можно есть их как помидорки)

ну ещё можешь ds_list использовать вместо массива и ds_map для навигации между менюшками

var temp_list;
menu = ds_map_create();

temp_list = ds_list_create();
temp_list[| 0]="Start";
temp_list[| 1]="Continue";
temp_list[| 2]="Exit";
menu[? "main"] = temp_list; //id списка записали в map, типа как указатели в Си

temp_list = ds_list_create(); // функция вернула новый id списка, старый лежит в menu[? "main"], так что не поникуем
temp_list[| 0]="Yes";
temp_list[| 1]="Now";
menu[? "confirm"] = temp_list ;

//Список удалять не нужно, т.к. его id хранится menu[? "confirm"]

И там дальше создаёшь всякие current_menu и current_select чтобы сделать менеджмент и отрисовку. Надеюсь, сам сможешь =)

Можно конечно сделать так, что ты добавишь переменную какое сейчас используется меню. А само создание\заполнение меню сделать в событии user_event(0) например. В нём, в зависимости от переменной менюшки, ты заполняешь свои menu и action.
Но мне структурно больше нравится вариант от Herzenrg c ds_map и ds_list.

И да, вот это "action[0]=StartGame();", если ты хотел использовать затем script_execute, то не нужно писать "()" в конце имени скрипта, а то это вызов этого скрипта, а не получение его id.

Ты можешь двумерный массив использовать

menu[0,0]="Yes"; action[0]=YesAction();
menu[0,1]="No"; action[1]=CloseMenu();
menu[1,0]="Start"; action[0]=StartGame();
menu[1,1]="Continue"; action[1]=ContinueGame();
menu[1,2]="Exit"; action[2]=EndGame();

Там даже функция для получения длины массива есть так что это не проблема.
Можно через дата-структуры пойти, но в GMS с ними работать запутанно. Когда 2.3 выйдет - там вообще пиздато станет - как с двумерными массивами можно будет работать.

Есть функции room_set_width и room_set_height насколько я помню для изменения размеров комнаты.

А ещё есть вкладка views - это то что тебе нужно как раз. Тебе нужно поставить галку на том чтобы их использовать, и для нужного вида ставишь галку enable this view, далее ты указываешь размер вида в комнате width/height и размер вьюпорта - как раз x1,x2,x3 от того что тебе нужно

Есть функции для кода все аналогочние. Типа views_enabled, view_visible[0] ну и на размеры и порты в справке посмотри. Так же есть фунции для изменения размеров окна. Только там нюанс есть. Если меняешь размер вида (не вьюпорт, а именно размер) или размер окна - тебе нужно будет cсделать surface_resize(application_surface,<width>,<height>) по новым размерам. Почему-то по дефолту этого не происходит, причём даже в GMS2.

Сосбтвенно, я и тебе и рекомендовал Hero Core и Remedy. Увы, подобных игр не знаю больше, они уникальные.

Я сам как-то давно делал Brain Storm:Tower Bombarde вдохновлявшись Hero Core. Но там без сложностей. НУ и моя последняя поделка на Gaminator, та что Мёртвая Звезда - тоже вдохновлялся Hero Core. Но когда я её сделаю - ваще ХЗ.

Из более мнее похожего только Environmental Station Alpha может быть.

Вершинный выполняется один раз на каждую вершину, пиксельный - один раз на каждый пиксель. В вершинном можно подменить координату вершины, а в пискельном - цвет и прозрачность пискеля. Это если вкратце.

Координаты UV передаются из вершинного шейдера в пиксельный через переменную типа varying, которая осуществляет интерполяцию UV-координат для всех пикселей. То есть в вершинном шейдере в неё идёт значение UV для каждой вершины, а в пиксельном из неё берётся уже растянутое равномерно по полигону (наподобие градиента) значение UV.

В шейдере можно работать в любом пространстве координат, хоть в мировом, хоть в локальном, хоть в экранном. Для перевода между ними используются соответствующие матрицы.

Но с UV там есть нюанс, что если не знаешь соотношение сторон спрайта, то не сможешь перейти в другие координаты, поэтому это соотношение надо передавать самому (или не его, а сами координаты уже посчитанные). Не помню, с чем это точно связано, так как шейдеры последний раз открывал давно... (то же самое про глубину, касался только слегка и уже забыл, как работать с буфером глубины в шейдерах).