Oleshe 38 Опубликовано: 30 сентября, 2023 (изменено) Привет! Последние 2 месяца я разрабатывал OE 2. Это проект очень схожий с Unity имея дочерний замысел с OpenGames где после изменения объекта не надо писать draw()... Он еще не готов, я хотел релизнуть после того как я его доделаю, но случилось не предвиденное. Из AppMarket-a была удалена либа opengames, что напрямую убивает весь первый движок т.к. оно в зависимостях, и в зависимостях всех дочерних приложений. Мне удалять это было не зачем, да он был поломанный, там объект кнопки не работал. Но это как-то тупо сносить один элемент из 2-, не так-ли? "Какая вообще разница?" Этот проект остался не доделанным, движок готов, установщик движка тоже. Не готов только редактор. Мне симпатизирует этот проект, но я прекращу (или сильно дестабилизирую) свою собственную поддержку данного проекта т.к. не исключаю случайное исчезновение моих продуктов в макрете. Так-же одна из причин это полу-мёртвое комьюнити. За пол года существования первого OpenGames им увлёкся только один человек. И то всё его творчество (хоть и довольно спорное (вирусы(удаление одной папки\файла))) хоть и было довольно спорное, но было удалено самим им. А зачем удалять свой продукт? Движок не нашел людей, а зачем разрабатывать предложение, если нету спроса. Это репозиторий проекта, в нём есть подробная (имхо) вики которая поясняет как работать в движке из скрипта. Так-же есть 2 примера которые могут перекидываться сообщениями между друг-другом. Там целая куча файлов которая добавляет функций в движок с определённой задачей. Цель этого поста: сказать что уже есть неплохие(имхо) наработки графического движка, если кто-то уже собрался что-то делать. Скриншот первой программы для того что-бы разбавить текст: Скрытый текст Изменено 6 ноября, 2023 пользователем Oleshe Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
eu_tomat 2 158 Опубликовано: 30 сентября, 2023 1 час назад, Oleshe сказал: Цель этого поста: сказать что уже есть неплохие(имхо) наработки графического движка, если кто-то уже собрался что-то делать. Тут есть смыл рассказать о возможностях этого движка, зачем он нужен, кому он может пригодиться, и как им пользоваться. 1 час назад, Oleshe сказал: За пол года существования первого OpenGames им увлёкся только один человек. Кстати, людей как раз и может привлечь хорошее описание. Какой бы хорошей ни была программа, пока ты о ней доходчиво не расскажешь, её даже скачивать, скорее всего, никто не будет. А убедить других программистов пользоваться своей библиотекой или движком будет ещё сложнее. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Oleshe Автор темы 38 Опубликовано: 1 октября, 2023 (изменено) 18 часов назад, eu_tomat сказал: Тут есть смыл рассказать о возможностях этого движка, зачем он нужен, кому он может пригодиться, и как им пользоваться. А, да, извините. Итак, для того что-бы им вообще можно было воспользоваться нужно загрузить Main.lua как библиотеку. Это делается примерно так: local OE = loadfile("/OpenGames 2/Main.lua")() Поскольку он "модульный" можно менять расположение главной папки. Оно будет записано в System.getUserSettings.OpenGames2EnginePath (Если вы устанавливали с установщика). Сделаем для примера радужный квадрат который по клику переместиться к курсору. Создадим наш квадрат, это панель. Существует множество разных объектов которые подвластны нам, такие как ComboBox, Button, Input, Switch и т.д. local obj = OE.createObject() -- Создаём объект которые сам добавляется в сцену. obj:setRenderMode(OE.Render.renderTypes.PANEL) -- Рендерить как панель. local matID = obj:addComponent(OE.Component.componentTypes.MATERIAL) -- Что-бы у нашего квадрата был цвет obj.Transform.Scale.Width = 10 -- Размер длинны obj.Transform.Scale.Height = 5 -- Размер высоты, в два раза меньше длинны После сделаем окно и покажем пользователю: OE.Project.Window.Color = 0x505050 OE.Project.Window.Width = 160 OE.Project.Window.Height = 50 OE.Project.Window.Title = "Это куб" OE.initWindow() -- Принять нынешние значения и пересоздать окно с ними Создадим файл для нашего скрипта и добавим его к нашему объекту: local function update(...) local args = {...} args[1].Components[matID].Color.First = math.random(0x0,0xFFFFFF) -- Именение цвета. Поскольку update вызываеться asap оно бует менять его довольно выстро. Что нам вроде-бы и надо. if args[2].lastEvent[1] == "drag" or args[2].lastEvent[1] == "drop" or args[2].lastEvent[1] == "touch" then -- lastEvent это буквально последнее событие args[1].Transform.Position.x = args[2].lastEvent[3]-math.ceil(args[1].Transform.Scale.Width/2)-args[2].Render.Window.x -- args[1] Это объект от имени которого запустился скрипт. args[2] это OE для вызываемых скриптов. То-есть мы находим середину для x относительно того куда мы кликнули, .. args[1].Transform.Position.y = args[2].lastEvent[4]-math.ceil(args[1].Transform.Scale.Height/2)-args[2].Render.Window.y -- .. как и для y end end OE.Storage.createFile(OE.CurrentScene.Storage,'Script.lua',{Update=update}) obj.Components[obj:addComponent(OE.Component.componentTypes.SCRIPT)].file = "Script.lua" -- addComponent вернёт номер компонента в obj.Components, а мы его сразу подхватываем и изменяем значение file для скриптa на наш файл. Оно найдёт его везде, в файлах сцены или файлах проекта. OE.Script.Reload() -- Перезагружаем все наши скрипты добовляя наш. Добавим нашу милашку к рендеру и готово obj:addToRender() Итог: local OE = loadfile("/OpenGames 2/Main.lua")() local obj = OE.createObject() obj:setRenderMode(OE.Render.renderTypes.PANEL) local matID = obj:addComponent(OE.Component.componentTypes.MATERIAL) obj.Transform.Scale.Width = 10 obj.Transform.Scale.Height = 5 OE.Project.Window.Color = 0x505050 OE.Project.Window.Width = 160 OE.Project.Window.Height = 50 OE.Project.Window.Title = "Это куб" OE.initWindow() local function update(...) local args = {...} args[1].Components[matID].Color.First = math.random(0x0,0xFFFFFF) if args[2].lastEvent[1] == "drag" or args[2].lastEvent[1] == "drop" or args[2].lastEvent[1] == "touch" then args[1].Transform.Position.x = args[2].lastEvent[3]-math.ceil(args[1].Transform.Scale.Width/2)-args[2].Render.Window.x args[1].Transform.Position.y = args[2].lastEvent[4]-math.ceil(args[1].Transform.Scale.Height/2)-args[2].Render.Window.y end end OE.Storage.createFile(OE.CurrentScene.Storage,'Script.lua',{Update=update}) obj.Components[obj:addComponent(OE.Component.componentTypes.SCRIPT)].file = "Script.lua" OE.Script.Reload() obj:addToRender() Теперь давайте сделаем тоже самое в сырую... Итог: local GUI = require("GUI") local System = require('System') local wk,win = System.addWindow(GUI.titledWindow(1,1,160,50,'Это куб',true)) win.backgroundPanel.colors.background = 0x505050 win.titlePanel.colors.background = 0x505050 local panel = win:addChild(GUI.panel(1,1,10,5,0x0)) wk.eventHandler = function(_,_,...) local args = {...} panel.colors.background = math.random(0,0xFFFFFF) if args[1] == 'touch' or args[1] == 'drop' or args[1] == 'drag' then panel.localX = args[3]-math.ceil(panel.width/2)-win.localX panel.localY = args[4]-math.ceil(panel.height/2)-win.localY end end win:draw() По символам он меньше. В обоих OE есть редактор в котором можно наглядно отредактировать расположение объектов, а в сыром запускай кучу раз. Минусы и плюсы использование движка: + Просто для освоения + Наглядно - Грамоздко (большие файлы) - События медленные (Разница между сырым и движком видна, причём очень сильно. Не знаю с чем это связано.) Можно еще сравнить Unity и какой ни-будь pygame. Разница размера файлов колоссальная, но большинство используют юнити т.к. в pygame и я не очень разобрался... Я не очень понимаю как находить сильные и слабые стороны в своём-же продукте... Изменено 1 октября, 2023 пользователем Oleshe 1 Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Oleshe Автор темы 38 Опубликовано: 10 мая, 2025 Шли года... Буксующая разработка продолжалась.. И вот 0.4! Считаю её стабильной и лучшей на данный момент, потому отметим её. Забудьте всё, теперь всё по-другому. Разберём попытку перенести одну игру, но провалившеюся по одной причине, которую мы разберём ниже. Здесь находится Main.lua. Разберём что же творится! Скрытый текст local scriptPath = string.gsub(require('System').getCurrentScript(),'/LiminalStarwave.app','') local args = {...} _, args = require('System').parseArguments(table.unpack(args)) local OE, why = assert(loadfile(scriptPath))() -- Подгрузка библиотеки if not OE then print(why) return end OE.applicationRoot = string.gsub(require('System').getCurrentScript(),'Main.lua','') OE.fileRoot = OE.applicationRoot .. 'Additional_Content/' -- Ставим рут приложения и папки контента OE.createScene('Game') -- Создаём основную сцену OE.Render.addLayer() -- Main game OE.Render.layers[2].y = -16 OE.Render.addLayer() -- Arrows and fly windows OE.Render.setResolution(135,50) -- Добавляем еще 2 слоя к первому, итого 3. 1 - задник, 2 - игра, 3 - UI Ставим разрешение 135 на 50. local sky = OE.createObject('Skybox','Game') -- Задник sky:_addScript('MAIN_Panel.lua',"Render") -- Добавляем скрипт панели sky.Render.color = 0 sky.Transform.Scale = {w = 135, h = 50} -- Параметры панели. Её размер и цвет. local BL = OE.createObject('BottomLine','Game') -- Две чёрные полосы сверху и снизу экрана по такому же принципу, как и задник BL:_addScript('MAIN_Panel.lua',"Render") BL.Render.color = 0 BL.Render.layer = 3 -- .. с отличием, что мы ставим ему 3 слой BL.Transform.Scale = {w = 135, h = 3} BL.Transform.Position = {y=48} local UL = OE.createObject('UpperLine','Game') UL:_addScript('MAIN_Panel.lua',"Render") UL.Render.color = 0 UL.Render.layer = 3 UL.Transform.Scale = {w = 135, h = 3} local BG = OE.createObject('Background','Game') -- Пикча задника игры. BG:_addScript('MAIN_Sprite.lua','Render') BG.Render.sourceImage = 'Background.pic' BG.Render.transperent = true -- Она прозрачна. BG.Render.layer = 2 local ENA = OE.createObject('ENA','Game') -- Наш персонаж ENA:_addScript('MAIN_Sprite.lua','Render') ENA.Render.sourceImage = 'ENAIdle1.pic' ENA.Render.transperent = true ENA.Render.layer = 2 ENA:_addScript('CharactersSpriteBehavior.lua','SpriteBehavior') -- Смотреть в следующем файле. Управляет анимацией спрайта. ENA:_addScript('PlayerInput.lua','PlayerInput') -- Смотреть в очередном следующем файле. Управляет вводом игрока. --ENA:_addScript('SoundManager.lua','Audio') -- Смотреть в третьем файле. Управляет звуком. ENA.Transform.Position = {y=32} OE.Storage.reloadAdditionalFiles(OE.fileRoot) -- Загружаем дополнительные ассеты из нашей папки OE.log('Project init completed') OE.loadScene("Game") -- Загружаем сцену OE.log('Scene loaded') OE.initWindow() -- И иницилизируем окно ./Additional_Content/Scripts/CharactersSpriteBehavior.lua local currentAnim = "" -- Анимация, что сейчас воспроизводится function Idle() currentAnim = "ENAIdle" Object.SpriteBehaviorAnimation.Play(currentAnim) end function Left() currentAnim = "ENALeft" Object.SpriteBehaviorAnimation.Play(currentAnim) end function Right() currentAnim = "ENARight" Object.SpriteBehaviorAnimation.Play(currentAnim) end function Down() currentAnim = "ENADown" Object.SpriteBehaviorAnimation.Play(currentAnim) end function Up() currentAnim = "ENAUp" Object.SpriteBehaviorAnimation.Play(currentAnim) end function ChangeSprite(index) Object.Render.setImage(currentAnim .. index .. ".pic") -- Сделал универсально. Анимация ENAIdle, индекс 1, .pic > ENAIdle1.pic end function OnEndNotIdle() OE.Script.Invoke(Idle, 0.2) -- В отличий от идле анимаций, другие чуть задержаться на последнем спрайте. end Start = Idle -- Когда сцена загрузится, мы переходим в Idle function Init() Object:_addScript('MAIN_Animation.lua','SpriteBehaviorAnimation', true) -- При иницилизвации скрипта, добавляем компонент анимации. Просто выполняет функций по таймкоду Object.SpriteBehaviorAnimation.animations["ENAIdle"] = { onEnd = Idle, -- Выполнится, когда кейфреймов не останится frames = { [0] = ChangeSprite, [0.2] = ChangeSprite, [0.4] = ChangeSprite } } Object.SpriteBehaviorAnimation.animations["ENAUp"] = { onEnd = OnEndNotIdle, frames = { [0] = ChangeSprite, [0.1] = ChangeSprite -- У нас только 2 спрайта на каждое действие, в отличий от Idle } } Object.SpriteBehaviorAnimation.animations["ENADown"] = Object.SpriteBehaviorAnimation.animations["ENAUp"] -- Ничем не отличаются, ибо мы уже сделали всё достатачно универсальным Object.SpriteBehaviorAnimation.animations["ENALeft"] = Object.SpriteBehaviorAnimation.animations["ENAUp"] Object.SpriteBehaviorAnimation.animations["ENARight"] = Object.SpriteBehaviorAnimation.animations["ENAUp"] end ./Additional_Content/Scripts/PlayerInput.lua local KeyCodes, pauseToggle = OE.Input.keyCodes local anims = {[KeyCodes.A] = "Left", [KeyCodes.S] = "Down", [KeyCodes.D] = "Right", [KeyCodes.W] = "Up"} -- Привязываем клавиши к соответствующим названиям функций, что стартуют анимации function KeyDown(key) -- Когда клавиша нажата if anims[key] then -- Если клавиша действия, значит Object.SpriteBehavior[anims[key]]() -- запускаем действие elseif key == KeyCodes.SPACE then -- , или же, если это пробел if pauseToggle then -- — ставим на паузу, остарнавливая аудио, или же снимаем с паузы Time.scale = 0 Object.Audio.Pause() else Time.scale = 1 Object.Audio.Resume() end pauseToggle = not pauseToggle -- Переключаем паузу end end ./Additional_Content/Scripts/SoundManager.lua local cmp, getFile,len1,len2 = require('component'), OE.Storage.getFile local empty = string.rep("\xAA",8192) -- Заранее подготавливаем пустую для кассет строку local listOfTapes = cmp.list('tape_drive') -- Получаем и tape1 = cmp.proxy(listOfTapes()) -- ставим прокси для обоих кассетниц tape2 = cmp.proxy(listOfTapes()) local lengts = {} -- Хз зачем if not tape1 or not tape2 then -- Если кассетниц менее 2х OE.log("Not enough tapes") return false end function Start() tape1.play() tape2.play() end function Update() -- Каждый кадр мы проверяем if tape1.getPosition() >= len2 then -- не закончилось ли аудио tape1.stop() tape2.stop() OE.exit() -- И если да — останавливаем звук и выходим -- BSOD picture, sound, crash system end end function Pause() tape1.stop() tape2.stop() end Resume = Start function Wipe() tape1.seek(-math.huge) tape2.seek(-math.huge) local k = tape1.getSize() for i = 1, k + 8191, 8192 do tape1.write(empty) tape2.write(empty) end tape1.seek(-math.huge) tape2.seek(-math.huge) end function Dissolve() Pause() Wipe() end onApplicationExit = Dissolve -- При стандартном выходе также останавливаем и очищаем кассету Dissolve() tape1.seek(-math.huge) tape2.seek(-math.huge) tape1.write(getFile('Inst.dfpwm')) -- Записываем инструментал len1 = #getFile('Inst.dfpwm') OE.Storage.unloadFile("Inst.dfpwm") -- Выгружаем его из памяти tape2.write(getFile('Voices.dfpwm')) -- Делаем тоже самое с голосами типо len2 = #getFile('Voices.dfpwm') OE.Storage.unloadFile("Voices.dfpwm") tape1.seek(-math.huge) tape2.seek(-math.huge) Получается, все старые проекты работать не будут. Список изменений.. Большой. Если кратко: Изменён рендер в очередной раз, тотально. Теперь он быстрей и работает на нашем движке Matrix, который можно выдернуть из движка прям так и использовать в своих сторонних проектах. Добавлена поддержка сети, который точно также можно вырвать. У него еще есть OpenOS версия!! Просто измените пару строк в получении евентов и добавлении слушателей. Добавлены компоненты: спрайт, кнопка(Как поставить кнопку, можете узнать в Test.app/Main.lua) Все модули перенесены в отдельную для этого папку. Самое модное: использование скриптов. Вы могли заметить, что мы ставим глобальные переменные и забираем их из, казалось бы, ниоткуда. Все скрипты теперь таблица. Глобальные переменные можно получить по ней. Таблицы привязываются к объекту. Следовательно, можно сделать так: OE.CurrentScene.Objects[1].Render.setColor(math.random(0,0xFFFFFF)) Или из самого скрипта: Object.Render.setColor(math.random(0xFFFFFF)) Весь рендер перенесён с screen библиотеки на сырые вызовы гпу. С матриксом это не такая проблема, если рендер стоит в режиме perObject. Следовательно, был исправлен воистину тупой баг, что на прозрачных пикслеях изображения не оставались символы, из-за чего прозрачностью обладал только задник пикселя, а символ оставался пробелом, кушая концепт прозрачности. Проблемы: Слои в Render.layers никак не связаны, как бы это странно не звучало. Придётся придумать альтернативу дублированию библиотеки и рисовать в иерархии, ибо если, например, изменится 1й слой, пиксели на месте 2го исчезнут, ибо они буквально на другом холсте и не считаются для колайда и он считает, что всё ок. Главная проблема: лаги. Из-за отрисовки изображения выяснилось, что рисовать такой большой спрайт даже в буфере — дорого. Очень. Идея для фикса уже есть, но, всё же, исправит ситуацию он не сильно, ибо всё сейчас упирается в скорость цикла. Такие пироги. Вот видео, например: Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taoshi 62 Опубликовано: 20 мая, 2025 В виду низкого call-бюджета реализовать полноэкранную анимацию с приемлемым fps не представляется. возможным. Но совмещая gpu.copy и редко используемые gpu.bitblt можно добиться определённого успеха в борьбе за частоту обновления игровой картинки. Если к этому добавить смекалку и учесть, что мод OpenComputers обычно используется внутри майнкрафта, то можно физически блокировать обзор частей экрана и использовать их как быстый буфер для хранения спрайтов выводимых на обозримую пользователем часть экрана. Например, в игре поддерживаются разрешения для экрана 3-го тира находящиеся в пределах 8000 символов. То есть можно установить разрешение экрана как 160 х 50, так и 100 х 80! (так же поддерживается в последних версиях Оцелота). И если спрятать половину этой площади (допустим, под уровнем пола помещения где располагается экран), то в обозримой части экрана всё ещё останется 40 строк (200 х 160 точек). Конечно, это уже не 256 х 192: полноценных портов Танчиков, Марио, Контры, Кастлевании и прочих шедевров не сваять. Но несмотря на это - появляется, пусть и изначально дефектный, но несмотря на это вполне работоспособный механизм для построения простых игр на спрайтах. И на основе того же самого Super Mario Bros можно сваять продукт с приемлемым глазу fps. Экран будет чуть меньше, чем в ниже приведённом примере, а процесс плавнее. Впрочем, у каждого продукта свои задачи. Возможно, кто-то делает аналог Юнити для пентиума-2, как знать.... А вот и ссылка на проект с использованием gpu.copy: https://www.reddit.com/r/OpenComputers/comments/olo832/i_remade_super_mario_bros_on_opencomputers/ Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Oleshe Автор темы 38 Опубликовано: 23 мая, 2025 Да-да, поразмыслив, кажется, использовать буферы на отдельное изображение — оно. Только проблема скорее в динамичности и объёме изображения. Если изменится элемент выше нашего, или, если он прозрачный, под ним, придётся ВСЁ пересчитывать, что как раз-таки очень дорого, даже если отрисовывать только ту область, что изменилась (Что и происходит в примере) Тем не менее, со статичной картинкой работать проще... Заранее просчитать и добавить в буфер все варианты спрайтов, а после доставать их на нужное место. Но с динамичной получится слишком много вариантов, из-за чего это увы полный. Что поделать... Пойду действительно попробую реализовать это... Маленький рефакторинг, в третий раз переписать движок рендера... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах
Taoshi 62 Опубликовано: 24 мая, 2025 Если цель - просто искусство, то можно изменить параметры стоимости вызовов в настройках. А если юзер френдли без шаманства в конфигах - то да, раскадровыватьтдинамическую картинку, сверять какие поля 2х4 или 4х4 совпали и по ним делать статичную карту. Ведь какие-то её части всё равно будут неизменны. Конечно, составлять спрайт (допустим) 48х48 из символов дороже по времени. Но сохранять много целых кадров 48х48 дорого уже по занимаемой области на экране. В любом случае движок исполняющий анимацию уже есть, и благодаря ему рутина создания целых фреймов или фрагментированных кусков не столь страшна. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на других сайтах