План гри:
для початку складемо схематичну мапу рівнів гри
Створивши план, потрібно подумати про графіку, тобто як наша гра виглядатиме.
в нашому керівництву ми будемо створювати 2д гру з видом зверху:
Початок
Створіть новий проект в Gideros
в проекті створіть новий файл main.lua.
відкрийте файл і напишіть код:
print("Факел в руці, ви готові ввійти в старі руїни.")
Якщо ви запустите гру, в консолі output ви побачите таке:
main.lua is uploading. Uploading finished. Факел в руці, ви готові ввійти в старі руїни.
Підтримка різних розмірів екрану
Наведений нижче код встановлює розмір екрана та ландшафтну орієнтацію.
application:setLogicalDimensions(1200, 1920)
application:setOrientation(Application.LANDSCAPE_LEFT)
встановимо автоматичне масштабування гри під розмір екрану, режим letterbox вписуватиме гру під розмір екрану не обрізаючи краї
application:setScaleMode("letterbox")
Фон
Встановимо чорний колір фону.для зрчності сторимо константу чорного кольору
щоб присоїти константі значення треба замість == икористовати @
BLACK @ 0x1A1A1A
application:setBackgroundColor(BLACK)
Спрайти
додамо спрайт фон до гри:
сторимо проекті папку images, і помістим тди малюнок foreground.png
щоб нащ малюнок відобразити грі, ми поинні його додлати на голоний екран гри stage
local fg = Bitmap.new(Texture.new("images/foreground.png", true))fg:setX(1201)stage:addChild(fg)
Написи
додамо в гру напис назви локаціїYELLOW @ 0xDAD45Eсистемний шрифт не підтримє кирилицю том ми пищемо напис латинськими літерами
local title = TextField.new(nil, "The Long Dark")
title:setTextColor(YELLOW)
title:setPosition(1320, 100)
stage:addChild(title)
додамо в гру свій шрифт з підтримкою українських літер :
-створимо в проекті гри теку fonts і додамо в неї файл шрифта IMMORTAL.ttf
тепер ми зможемо використовати наш шрифт у грі замість системного
змінимо наш код видаливши напис інощемною і додамо напис українською.
FONT_MEDIUM @ \Font.new("fonts/IMMORTAL.ttf", 60, true)\ми також можемо використовати і мальовані шрифти
local title = TextField.new(FONT_MEDIUM, "Велика Темрява")
FONT_MEDIUM @ \Font.new("fonts/immortal_medium.txt", "fonts/immortal_medium.png")\
Кнопки упраління
сторимо кнопку руху вперед, і назвемо її north -пініч
наша кнопка буде у вигляді тексту "N"
local buttons = Sprite.new()local north = {}local up = TextField.new(FONT_LARGE, "N", true)local down = TextField.new(FONT_LARGE, "N", true)up:setTextColor(WHITE) down:setTextColor(BLACK)north = Button.new(up, down)north:setPosition(1500, 800) buttons:addChild(north)
у коді ми икористали клас кнопки Button.new, код цього класу є у прикладах які йдуть з Gideros.
отже створимо у нашому проекті папку clases, і просто скопіюємо туди файл класу button.lua з прикладу Gideros про використання кнопок.
Ми створили групу buttons - в яку будемо додаати всі наші кнопки, -тепер ми поинні додати нашу групу buttons на екран гри stage
stage:addChild(buttons)додайте в групу -інші кнопки захід, схід і південь - самостійно.
Події кнопок
Клас button який ми додали має подію при натисканні на кнопку- "click"
щоб виконати дію в грі при ідбуванні цієї події ми повинні створити "слухач події" -EventListener
north:addEventListener("click", function() print("ви натисли північ") end)
south:addEventListener("click", function() print("ви натисли південь") end)
west:addEventListener("click", function() print("ви натисли захід") end)
east:addEventListener("click", function() print("ви натисли схід") end)
покищо при натисканні кнопок нічого не відбувається, просто виведе в консоль output
наш текст
Підсумуємо
ми вже знаємо все щоб почати створюати власну гру
-додавати на екран спрайти
-додавати на екран кнопки
-виводити на екран написи
в наступному розділі ми створимо мапу і будемо по ній рухатись
а поки це все
------------------------------------
Сторимо власний клас
для початку реорганізуємо наш попередній код:
-сторимо файл Constants.lua і сі константи і макроси перемістим в нього
далі теці clases і створимо ноий файл Mainscreen.lua -
це буде наш клас Mainscreen
MainScreen = Core.class(Sprite)
function MainScreen:init()
end
тепер перемістим весь код крім application налаштуань та EventListener з main.lua в наш клас в MainScreen:init()
а main.lua додамо виклик нашого класу
local main = MainScreen.new()
stage:addChild(main)
в нашому класі змінимо всі посилання ‘stage’ на ‘self’
self.north = {}
self.south = {}
self.west = {}
self.east = {}
тепер ми можемо посилатися на об'єкти класу безпосередньо main.[змінна]
тож виправимо код подій
main.north:addEventListener("click", function() print("ви натисли north") end)така впорядкованість коду нам стане нагоді в подальшому, коли код розростеться нам буде легше з ним працювати.
main.south:addEventListener("click", function() print("ви натисли south") end)
main.west:addEventListener("click", function() print("ви натисли west") end)
main.east:addEventListener("click", function() print("ви натисли east") end)
таблиці-Масиви та тайли
TileMap - це клас для створення із тайлів ігрових рівнів
створимо папці clases файл TileMaps.lua і додамо в нього клас WorldMap
з двума таблицями.
WorldMap = Core.class(Sprite)Наш mapArrays буде мати два шари . LAYER_TERRAIN - для тайлів підлоги і LAYER_MONSTERS для тайлів монстрів і героя. self.mapArrays буде двома таблицями чисел.
function WorldMap:init()
self.mapArrays = {}
self.mapLayers = {}
end
Отже, щоб швидко визначити mapArrays:
self.mapArrays[LAYER_TERRAIN] = self:generateTerrain()додамо ноі константи в Constants.lua
self.mapArrays[LAYER_MONSTERS] = self:placeMonsters
LAYER_TERRAIN @ 1ми изначили розмір нашого ріня 20 *20 тайлів
LAYER_MONSTERS @ 2
LAYER_COLUMNS @ 20
LAYER_ROWS @ 20
далі в нашому класі створимо функцію генерування карти рівня з тайлів
function WorldMap:returnTileMap(array, tileset)для тайлів ми використовуатимемо такий атлас зоображень :
local texture = Texture.new(tileset, true)
local tileMap = TileMap.new(LAYER_COLUMNS, LAYER_ROWS, texture,
TILE_WIDTH, TILE_HEIGHT)
for y = 1, LAYER_ROWS do
for x = 1, LAYER_COLUMNS do
local tX = array[x + (y - 1) * LAYER_COLUMNS]
local tY = 1
tileMap:setTile(x, y, tX, tY)
end
end
return tileMap
end
підлога
---///---
монстри
x і y - це номер тайлу по горизонталі і вертикалі
tX і tY - це номер текстури тайлу на нашому атласі тайлів
додаємо атласи зоображень до теки з зоображеннями
і додаємо решту коду до WorldMap:init().
local group = Sprite.new()
local layer = self:returnTileMap(self.mapArrays[LAYER_TERRAIN],
"images/tileset-background-108px.png")
table.insert(self.mapLayers, layer)
group:addChild(layer)
layer = self:returnTileMap(self.mapArrays[LAYER_MONSTERS],
"images/tileset-monsters-108px.png")
table.insert(self.mapLayers, layer)
group:addChild(layer)
self:addChild(group)
виведемо наш рівень з тайлів на екран
для почотку експериментальним шляхом я змінив розмір тайліві зробив їх 108 пікселів, тож виправимо наші константи
TILE_WIDTH @ 108щоб наші тайли відобразились на екрані достатньо додати всього 2 рядки в файл main.lua.
TILE_HEIGHT @ 108
local world = WorldMap.new()Вжух, і нашу мапу з двох шарів тайлів тепер видно
stage:addChild(world)
Рух героя карті рівня
при натисканн кнопо напрямк нас просто иодиться текст в консоль. давай додамо до код функцю руху героя checkMovemain.north:addEventListener("click", function() checkMove(0, -1) end)-1 по Y - це рух на пвніч вгору
фнкця бде перевряти чи можна перемститись вгор і якщо можна запскатиме фнкцію moveHero
Local hero = {}hero.x, hero.y = 6, 6
local function checkMove(dx, dy)
world:moveHero(hero, dx, dy)
end
MoveHero в таблиці LAYER_MONSTERS змнює 1 на 0 і ставить 1 новому місці
function WorldMap:moveHero(heroX, heroY, dx, dy)в кінці також змінюємо координати тайла героя hero.x і hero.y
local array = self.mapArrays[LAYER_MONSTERS]
array[hero.x + (hero.y - 1) * LAYER_COLUMNS] = 0
self.mapLayers[LAYER_MONSTERS]:clearTile(heroX, heroY)
array[hero.x + dx + (hero.y + dy - 1) * LAYER_COLUMNS] = 1
self.mapLayers[LAYER_MONSTERS]:setTile(heroX + dx, heroY + dy, 1, 1)
for i = 1, #self.mapLayers do
local layer = self.mapLayers[i]
layer:setX(layer:getX() - (dx * TILE_WIDTH))
layer:setY(layer:getY() - (dy * TILE_HEIGHT))
end
hero.x = hero.x + dx
hero.y = hero.y + dy
end
Стіни
зараз наш герой може ходити не лише по плитці, а і по стінах і по монстрах -пора нам ввести змінн яка перевірятиме де герой може ходити а де ні, додамо до файл з константами циклопедію:cyclopedia = {тепер ми можемо додати в checkMove перевірку по циклопедії, що знаходиться на тому тайлі, та спочатку створимо функцію getTileKey в TileMaps.lua
["monsters"] =
{[1] = {name = "hero"},
[2] = {name = "goblin"},
[3] = {name = "bugbear"},
[4] = {name = "orc"},
["background"] =
{[1] = {name = "floor", blocked = false},
[2] = {name = "floor", blocked = false},
[3] = {name = "wall", blocked = true},
[4] = {name = "wall", blocked = true}}}
function WorldMap:getTileKey(x, y)
local index = x + (y - 1) * LAYER_COLUMNS
for n = #self.mapArrays, 1, -1 do
local array = self.mapArrays[n]
if array[index] ~= 0 then
return array[index], n
end
end
end
тепер перепишемо нашу фнкцію checkMove
local function checkMove(dx, dy)тепер коли ми рухаємося, в консолі виводиться текст циклопедії про те що знаходиться в цьом тайлі.
local entry, layer = world:getTileKey(hero.x + dx, hero.y + dy)
if layer == LAYER_MONSTERS then
local tile = cyclopedia.monsters[entry]
print("це тайл " .. tile.name)
elseif layer == LAYER_TERRAIN then
local tile = cyclopedia.background[entry]
if tile.blocked then
print("це тайл " .. tile.name)
else
world:moveHero(hero, dx, dy)
end
end
end
Підсумок
це кінець розділу 2, наступному розділі ми будемо формувати рівні процедурно
---------------------------
Процедурна генерація
процедурно ми можемо генерувати нескінченні світи атоматично
ось як ми бдемо генерати підлогу старих руїн , обираючи ипадкову текстуру полу
for y = 1, LAYER_ROWS doкоді ми прописали 10 різновидів піблоги, тож додамо додаткові малюнки тайлів підлоги тек з зоображеннями
for x = 1, LAYER_COLUMNS do
local i = index(x, y)
if f then
local choice = math.random(1, 10)
if choice ~= 2 then
choice = 1
end
end
array[i] = choice
end
end
return array
--створити масив для кожного шару розміру LAYER_ROWS * LAYER_COLUMNSтепер ми генерємо новий додатковий шар над шаром підлоги, щоб розміщати на ньом різні предмети WorldMap:addEnvironment()
self.mapArrays[LAYER_TERRAIN] = self:generateLevelTerrain()
self.mapArrays[LAYER_ENVIRONMENT] = self:addEnvironment()
-- призначити TileMaps в self.mapLayers
local group = Sprite.new()
local layer = self:returnTileMap(self.mapArrays[LAYER_TERRAIN],
"images/tileset-terrain-108px.png")
group:addChild(layer)
table.insert(self.mapLayers, layer)
local layer = self:returnTileMap(self.mapArrays[LAYER_ENVIRONMENT],
"images/tileset-environment-108px.png")
group:addChild(layer)
table.insert(self.mapLayers, layer)
function WorldMap:addEnvironment()створиши наш ріень процедурно, ми також можемо напонити ріень монстрами теж процедурно(рандомно)
local array = {}
local choice = 0
local p = 0
for y = 1, LAYER_ROWS do
for x = 1, LAYER_COLUMNS do
p = p + 1
if p > 11 then
p = 0
end
local i = index(x, y)
--тексткри тайлі 1 або 2 по периметру рівня
if x == 1 or y == 1 or x == LAYER_COLUMNS or y == LAYER_ROWS then
choice = math.random(1, 2)
-- кожну 11-ту плитку поставимо випадково текстуру руїн колони 3 або 4
elseif p == 0 then
choice = math.random(3, 4)
--випадкоо, покладіть текстуру ями (5) або криниці(6) на підлогу
else
choice = math.random(1, 80)
if choice ~= 5 and choice ~= 6 then
choice = 0
end
end
array[i] = choice
end
end
return array
end
--основні ігрові змінніми будемо знаходити пусті місця ріні де можна розмстити героя або монстрі WorldMap:placeMonsters і випадкоо генерувати монстрів або героя placeMonsters
local hero = {}
--просто x, y, щоб представляти героя на даний моментhero.x, hero.y = 6, 6
local monsters = {}
--створити шість випадкових монстрів
monsters.list = {{}, {}, {}, {}, {}, {}}
for key, m in ipairs(monsters.list) do
m.x, m.y, m.entry = 0, 0, math.random(2, 4)
end
--створити світ і призначити місця для героя та монстрів
local world = WorldMap.new(hero, monsters)
function WorldMap:placeMonsters(hero, monsters)ми переіряємо щоб монстри не розміщалиць один на одному , на колонах і на клітинці героя , теж саме пееіряємо і для героя
--це масив без монстрів
local mArray = {}
for y = 1, LAYER_ROWS do
for x = 1, LAYER_COLUMNS do
mArray[index(x, y)] = 0
end
end
local envArray = self.mapArrays[LAYER_ENVIRONMENT]
local x, y, i = 0, 0, 0
--знайти пусте місце для кожного монстра
for key, m in ipairs(monsters.list) do
repeat
x = math.random(2, LAYER_ROWS - 1)
y = math.random(2, LAYER_COLUMNS - 1)
i = index(x, y)
--повертається істинно, тільки якщо там немає монстрів, і це тайл підлоги
placed = (mArray[i] == 0) and (envArray[i] == 0)
until placed
-- додати до масиву
mArray[i] = m.entry
--і змінити координати монстра m.x, m.y = x, y
end
return mArray
end
Як світ зміщується
в кінці WorldMap:init(), нам також потрібно додати наступний кодself:shiftWorld(hero.x - 6, hero.y - 6)WorldMap:shiftWorld(), зміщватиме весь світ так, щоб герой залишався в центрі екрана при русі
Туман і Зона видимості героя
Коли ми переміщуємося картою, ми не повинні бачити одразу всю карту, ми повинні бачити лише зону навколо героя, введемо в гру туман
Припустимо наш світ виглядає так:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 @ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 M 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 M 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Але ми тільки хочемо освітлити невелику територію навколо героя. Тоді все, що нам дійсно потрібно зробити, це накладання світлої карти, де стоїть наш герой.
0 0 2 1 2 0 0 0 1 1 1 1 1 0 2 1 1 1 1 1 2 1 1 1 1 1 1 1 2 1 1 1 1 1 2 0 1 1 1 1 1 0 0 0 2 1 2 0 0Давайте в нашу гру додамо ще 1 шар - шар освітлення:
function WorldMap:init(hero, monsters)Тайли туману виглядають так
self.mapArrays = {}
self.mapLayers = {}
self.mapArrays[LAYER_TERRAIN] = self:generateLevelTerrain()
self.mapArrays[LAYER_ENVIRONMENT] = self:addEnvironment()
self.mapArrays[LAYER_MONSTERS] = self:placeMonsters(hero, monsters)
self.mapArrays[LAYER_LIGHT] = self:addLight(hero)
local group = Sprite.new()
local layer = self:returnTileMap(self.mapArrays[LAYER_TERRAIN],
"images/tileset-terrain-108px.png")
group:addChild(layer)
table.insert(self.mapLayers, layer)
layer = self:returnTileMap(self.mapArrays[LAYER_ENVIRONMENT],
"images/tileset-environment-108px.png")
group:addChild(layer)
table.insert(self.mapLayers, layer)
layer = self:returnTileMap(self.mapArrays[LAYER_MONSTERS],
"images/tileset-monsters-108px.png")
group:addChild(layer)
table.insert(self.mapLayers, layer)
layer = self:returnTileMap(self.mapArrays[LAYER_LIGHT],
"images/tileset-light-108px.png")
group:addChild(layer)
table.insert(self.mapLayers, layer)
self:addChild(group)
self:shiftWorld(hero.x - 6, hero.y - 6)
end
Це 4 тайла з різною прозорістю, 1тайл повністю прозорий(ви його не бачите), 4й тайл повністю непрозорий
Наш 4й шар створюється викликом WorldMap:addLight(hero).
function WorldMap:addLight(hero)
--наповніть світловий масив темними тайлами
local lArray = {}
for y = 1, LAYER_ROWS do
for x = 1, LAYER_COLUMNS do
lArray[index(x, y)] = 4
end
end
local torch = {
0, 0, 2, 1, 2, 0, 0,
0, 1, 1, 1, 1, 1, 0,
2, 1, 1, 1, 1, 1, 2,
1, 1, 1, 1, 1, 1, 1,
2, 1, 1, 1, 1, 1, 2,
0, 1, 1, 1, 1, 1, 0,
0, 0, 2, 1, 2, 0, 0}
--накладення героя масивом освітлення
local torchIndex = 0
for y = hero.y - 3, hero.y + 3 do
for x = hero.x - 3, hero.x + 3 do
torchIndex = torchIndex + 1
local i = index(x, y)
--змінюйте масив світла, якщо лише на екрані if i > 0 and i < #lArray and torch[torchIndex] ~= 0 then
lArray[i] = torch[torchIndex]WorldMap:addLight трохи неірно названо, воно фактично затемнює всю карту, освтлюючи лише зону біля героя
end
end
end
return lArray
end
Циклопедія
Перш ніж закінчити цей підручник, давайте поговоримо про нашу циклопедію в Constants.lua. це просто таблицея Lua, з якої ми витягуємо інформацію, щоб визначити тайли наших масивів карт.cyclopedia = {В CheckMove ми використовуємо WorldMap:getTileKey щоб отимати індекс тайлу, а потім за цим індексом витягнути інформацію про нього з циклопедії.
["monsters"] = {
[1] = {name = "hero"},
[2] = {name = "goblin"},
[3] = {name = "bugbear"},
[4] = {name = "orc"},},
["background"] = {
[1] = {name = "floor", blocked = false},
[2] = {name = "floor", blocked = false},
[3] = {name = "wall", blocked = true},
[4] = {name = "wall", blocked = true},}}
local entry, layer = world:getTileKey(hero.x + dx, hero.y + dy)наприклад cyclopedia.monsters[2] витягує з циклопедії значення {name = “goblin”}.
if layer == LAYER_MONSTERS then
local tile = cyclopedia.monsters[entry]
cyclopedia:getEntry("monsters", entry)
буде робити те саме саме, але тепер циклопедія буде локальним екземпляром класу Cyclopedia, і getEntry буде функцією в цьому класі. Якщо ми програмуємо цей клас саме так, це дозволить
cyclopedia:getEntry("monsters", "goblin")що є основною причиною, чому ми повинні це зробити. Таким чином, ми можемо просто шукати матеріал безпосередньо, замість того, щоб завжди пам'ятати, який запис відповідає якому номеру в кожному списку.
Для цього ми просто передаємо все в клас. Тут перейменуємо циклопедію як manual а потім помістіть його в Cyclopedia:init() функцію. Що буде робити наш клас Cyclopedia, це пройти через manual змінну і призначити кожне значення self.lists key
Cyclopedia = Core.class()
function Cyclopedia:init()
self.lists = {}
local manual = {
["terrain"] = {
[1] = {name = "stone floor"},
[2] = {name = "stone floor"},
[3] = {name = "stone floor"},
[4] = {name = "stone floor"},},
["environment"] = {
[1] = {name = "wall", blocked = true},
[2] = {name = "wall", blocked = true},
[3] = {name = "pillar", blocked = true},
[4] = {name = "pillar", blocked = true},
[5] = {name = "dirt hole", blocked = false},
[6] = {name = "well", blocked = false},},
["light"] = {
[1] = {name = "bright"},
[2] = {name = "dim"},
[3] = {name = "dark/remembered"},
[4] = {name = "unexplored"},
[5] = {name = "white"},
[6] = {name = "black"},},
["monsters"] = {
[1] = {name = "hero"},
[2] = {name = "goblin"},
[3] = {name = "orc"},
[4] = {name = "bugbear"},},
}
for key, val in pairs(manual) do
self.lists[key] = val
end
end
На цьому етапі все, що ми зробили, ускладнює нашу змінну циклопедії, поставивши її в клас, а потім призначивши її self.lists.Ми взяли нашу таблицю і помістили його в змінну book , а потім призначили його екземпляру таблиці.Тепер потрібно написати власну функцію getEntry для безпосереднього отримання інформації.
function Cyclopedia:getEntry(list, entry)
local cyclopedia = self.lists[list]
local isNumber = tonumber(entry)
if isNumber then
return cyclopedia[entry]
else
for key, val in pairs(cyclopedia) do
if val.name == entry then
return val
end
end
end
endЯкщо ми викличемо Cyclopedia: getEntry () із записом, який є числом, він функціонує точно так, як це було раніше Тобто, він повертає значення, пов'язане з цим номером.
Але якщо ми викличемо це записом, назви, то ми одержуємо таке саме значення.
manual:getEntry("light", "dim")Тепер ми можемо додати практично всю інформацію про нашу корисну гру вручну та подивитися її прямо. Визначення різних джерел світла можна зробити так
["light-source"] = {Дамо героєві torch (факел) в Main.lua :
[1] = {name = "torch", radius = 3, array = {
0, 0, 2, 1, 2, 0, 0,
0, 1, 1, 1, 1, 1, 0,
2, 1, 1, 1, 1, 1, 2,
1, 1, 1, 1, 1, 1, 1,
2, 1, 1, 1, 1, 1, 2,
0, 1, 1, 1, 1, 1, 0,
0, 0, 2, 1, 2, 0, 0}},},
manual = Cyclopedia.new()---------------------------
hero.light = manual:getEntry("light-source", "torch")
в Натупному розділі Атака
Немає коментарів:
Дописати коментар