Шукати в цьому блозі

Керівнитство створення РПГ


План гри:


для початку складемо схематичну мапу  рівнів гри

Створивши план, потрібно подумати про графіку, тобто як наша гра виглядатиме.
в нашому  керівництву ми будемо створювати 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)

function WorldMap:init()
  self.mapArrays = {}
  self.mapLayers = {}
end
Наш mapArrays буде мати два шари . LAYER_TERRAIN - для тайлів підлоги і LAYER_MONSTERS для тайлів монстрів і героя.  self.mapArrays буде двома таблицями чисел.

Отже, щоб швидко визначити mapArrays:
self.mapArrays[LAYER_TERRAIN] = self:generateTerrain()
self.mapArrays[LAYER_MONSTERS] = self:placeMonsters
додамо ноі константи в Constants.lua


LAYER_TERRAIN @ 1 
LAYER_MONSTERS @ 2 
LAYER_COLUMNS @ 20 
LAYER_ROWS @ 20
ми изначили розмір нашого ріня   20 *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[+ (- 1) * LAYER_COLUMNS]
      local tY = 1
      tileMap:setTile(x, y, tX, tY)
    end
  end
  return tileMap
end
для тайлів ми використовуатимемо такий атлас зоображень :




підлога

---///---

монстри

Зверніть увагу на  tileMap:setTile(x, y, tX, tY)
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
TILE_HEIGHT @ 108
щоб  наші тайли відобразились на екрані достатньо додати всього 2 рядки в файл main.lua.
local world = WorldMap.new()  
stage:addChild(world)
Вжух, і нашу мапу з двох шарів тайлів тепер видно


Рух героя  карті рівня 

при натисканн кнопо  напрямк  нас просто иодиться текст в консоль. давай додамо до код функцю руху  героя  checkMove  
main.north:addEventListener("click", function() checkMove(0, -1) end)
-1 по Y   - це рух на пвніч вгору
фнкця бде перевряти чи можна перемститись вгор і якщо можна запскатиме фнкцію moveHero
 Local hero = {}
hero.x, hero.= 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)

  local array = self.mapArrays[LAYER_MONSTERS]
  array[hero.+ (hero.- 1) * LAYER_COLUMNS] = 0
  self.mapLayers[LAYER_MONSTERS]:clearTile(heroX, heroY)

  array[hero.+ dx + (hero.+ 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.= hero.+ dx
  hero.= hero.+ dy 
end
в кінці також змінюємо координати тайла героя hero.x і hero.y

Стіни

зараз наш герой може ходити не лише по  плитці, а і по стінах і по монстрах -пора нам ввести змінн яка перевірятиме де герой може ходити а де ні, додамо до файл з константами  циклопедію:
cyclopedia = {
  ["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}}}
тепер ми можемо додати в checkMove  перевірку по циклопедії, що знаходиться на тому тайлі, та спочатку створимо  функцію getTileKey в TileMaps.lua 

function WorldMap:getTileKey(x, y) 
  
  local index = x + (- 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.+ dx, hero.+ 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
  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
 коді ми прописали 10 різновидів піблоги, тож додамо додаткові малюнки тайлів підлоги  тек з зоображеннями


і нову функцію  addEnvironment()
--створити масив для кожного шару розміру LAYER_ROWS * LAYER_COLUMNS 
 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)
тепер ми  генерємо новий додатковий  шар над шаром підлоги, щоб розміщати на ньом різні предмети  WorldMap:addEnvironment() 
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
створиши наш ріень процедурно, ми також можемо напонити ріень монстрами теж процедурно(рандомно)

--основні ігрові змінні
local hero = {}
--просто x, y, щоб представляти героя на даний моментhero.x, hero.= 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)
ми будемо знаходити пусті місця  ріні де можна розмстити героя або монстрі   WorldMap:placeMonsters і випадкоо генерувати  монстрів або героя  placeMonsters  

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.= x, y
 end

 return mArray
end
ми переіряємо  щоб монстри не розміщалиць  один на одному , на колонах і на клітинці героя , теж саме пееіряємо і для героя


Як світ зміщується

в кінці WorldMap:init(), нам також потрібно додати наступний код
self:shiftWorld(hero.- 6, hero.- 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.- 6, hero.- 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.- 3, hero.+ 3 do
    for x = hero.- 3, hero.+ 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]
      end
    end
  end
  return lArray
end
WorldMap:addLight  трохи неірно названо, воно фактично затемнює всю карту, освтлюючи лише зону біля героя

Циклопедія

Перш ніж закінчити цей підручник, давайте поговоримо про нашу циклопедію в Constants.lua.  це просто таблицея Lua, з якої ми витягуємо інформацію, щоб визначити тайли  наших масивів карт.
cyclopedia = {
 ["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},}}
В  CheckMove ми використовуємо WorldMap:getTileKey щоб отимати  індекс тайлу, а потім за цим індексом витягнути інформацію про нього з циклопедії.
 local entry, layer = world:getTileKey(hero.+ dx, hero.+ dy) 
 if layer == LAYER_MONSTERS then 
   local tile = cyclopedia.monsters[entry]
наприклад cyclopedia.monsters[2] витягує  з циклопедії значення {name = “goblin”}.  

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 () із записом, який є числом, він функціонує точно так, як це було раніше Тобто, він повертає значення, пов'язане з цим номером.
Але якщо ми викличемо це записом, назви, то ми одержуємо таке саме значення.

Якщо нам треба  прочитати тайл світла, ми можемо використовувати "dim "
manual:getEntry("light", "dim")
Тепер ми можемо додати практично всю інформацію про нашу корисну гру вручну та подивитися її прямо. Визначення різних джерел світла можна зробити так
["light-source"] = {
    [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}},},
Дамо героєві  torch  (факел) в Main.lua :
manual = Cyclopedia.new()
hero.light = manual:getEntry("light-source", "torch")
---------------------------

в Натупному розділі  Атака

Немає коментарів:

Дописати коментар