Давненько была статья про Gideros (разработка игр на LUA), но продолжения я так и не нашел. Поэтому сделал небольшую статейку о том, как разрабатывать игры на Gideros Studio. Исходники и апкшник проекта в конце статьи. Продолжение под катом.



Установка


Все предельно просто, переходим по этой ссылке и скачиваем необходимый установочник. В моем случае Gideros 2015.08 для OS X.

Теперь стандартная процедура перетаскивания приложения в Application.

image

Переходим в приложения и открываем Gideros Studio:

image
Начальный экран
image


Делаем простую игру


Начнем мы с классики, напишем «Hello, world!» на экране.

Для начала создадим проект:

1. Нажимаем «Create New Project»:

image

2. Вводим название проекта (в моем случае «Hi»):

image

3. Создаем файл для нашего скрипта:

image

image

Ну и обещанный «Hello, world!»:
Код
-- application:getDeviceHeight() - высота экрана
-- application:getDeviceWidth() - ширина экрана
-- textfield:getWidth() - ширина спрайта, т.е. текста
-- textfield:getHeight() - высота спрайта, т.е. текста

local half_height = application:getDeviceHeight() / 2
local half_width = application:getDeviceWidth() / 2

local textfield = TextField.new(nil, "Hello, world!")

textfield:setX(half_width - textfield:getWidth() / 2)
textfield:setY(half_height - textfield:getHeight() / 2)

stage:addChild(textfield)


Нажимаем на джойстик и у нас запускается Gideros Player:
Скрины
image

image

Теперь нажимаем на синий треугольник и в плеере запускается наш скрипт.
Скрины
image

image

Чтобы остановить выполнение проекта нажмите на красный восьмиугольник. Теперь сделаем что-нибудь серьезнее. Например, подобие Angry Birds.

1. Подготовка:

Нам понадобится SceneManager для переключения между уровнями. Берем его отсюда, нам нужны файлы scenemanager.lua и easing.lua. Загружаем их в наш проект (для удобства скопируйте его в папку проекта) при помощи «Add Existing Files...»:



И выбираем наши скрипты из папки проекта.

Теперь подготовим необходимые нам изображения, проделайте ту же самую процедуру как и со скриптами. Берем их отсюда:
bg.png — yadi.sk/i/LLZpkzBliRDD9
buttons.png — yadi.sk/i/96GChQSAiRDCz
props.png — yadi.sk/i/JaM6n6ZqiRDD7
spritesheet.png — yadi.sk/i/n6siot0LiRDHV

2. Just do it!

Создаем скрипт main.lua, который у нас служит для запуска уровня:
Код
application:setOrientation(application.LANDSCAPE_LEFT)

sceneManager = SceneManager.new({
	["level"] = level
})

stage:addChild(sceneManager)

--start level scene
sceneManager:changeScene("level", 1, SceneManager.flipWithFade, easing.outBack)


Теперь создаем наш главный скрипт с уровнем level.lua:
Код
-- Add box2d physics library
require "box2d"

level = Core.class(Sprite)

function level:init()
	
	application:setOrientation(Application.LANDSCAPE_LEFT) 
	local spritesheet = Texture.new("spritesheet.png")
	local props = Texture.new("props.png")
	local buttons = Texture.new("buttons.png")
	
	self.world = b2.World.new(0, 10, true)
	
	-- Globals for followed camera
	self.screenW = application:getContentWidth()*2
	self.screenH = application:getContentHeight()
	
	-- Add sprites
	self.catapult_l = Bitmap.new(TextureRegion.new(spritesheet, 834, 1, 43, 124))
	self.catapult_r = Bitmap.new(TextureRegion.new(spritesheet, 3, 1, 37, 199))
	self.bird_idle = Bitmap.new(TextureRegion.new(spritesheet, 903, 798, 46, 44))
	self.bg_image = Texture.new("bg.png", true, {wrap = Texture.REPEAT})
	self.square_prop = Bitmap.new(TextureRegion.new(props, 0, 1, 84, 84))
	self.triangle_prop = Bitmap.new(TextureRegion.new(props, 85, 1, 85, 83))
	self.restart = Bitmap.new(TextureRegion.new(buttons, 580, 295, 108, 108))
	
	-- Change scale of sprites
	self.bird_idle:setScale(0.6, 0.6, 0.6)
	setHalf({self.catapult_r, 
		self.catapult_l,
		self.square_prop,
		self.triangle_prop,
		self.restart})
	
	-- Create background
	local bg = Bitmap.new(self.bg_image)
	bg:setPosition(0, 0);
	local bg1 = Bitmap.new(self.bg_image)
	bg1:setPosition(bg:getWidth(), 0);
	
	-- Add ground collision
	self:wall(application:getContentWidth(), application:getContentHeight() - 20, application:getContentWidth()*2, 40)
	
	-- Add props physics
	local square_body = self.world:createBody{type = b2.DYNAMIC_BODY}
	local square_shape = b2.PolygonShape.new()
	square_shape:set(0, 0,
		self.square_prop:getWidth(), 0,
		self.square_prop:getWidth(), self.square_prop:getHeight(),
		0, self.square_prop:getHeight())
	local square_fixture = square_body:createFixture{shape = square_shape, density = 1.0, friction = 0.1, restitution = 0.2}
	self.square_prop.body = square_body
	
	local triangle_body = self.world:createBody{type = b2.DYNAMIC_BODY}
	local triangle_shape = b2.PolygonShape.new()
	triangle_shape:set(self.triangle_prop:getWidth() / 2, 0,
		self.triangle_prop:getWidth(), self.triangle_prop:getHeight(),
		0, self.triangle_prop:getHeight())
	local triangle_fixture = triangle_body:createFixture{shape = triangle_shape, density = 1.0, friction = 0.1, restitution = 0.2}
	self.triangle_prop.body = triangle_body
	
	-- Place catapult
	self.catapult_r:setPosition(100, 178)
	self.catapult_l:setPosition(85, 174)
	
	-- Place bird
	self.bird_idle:setAnchorPoint(.5, .5)
	self.bird_idle:setPosition(100, 190)
	self.start_x, self.start_y = self.bird_idle:getPosition()
	
	-- Place props
	self.square_prop.body:setPosition(600, 200)
	self.triangle_prop.body:setPosition(600, 150)
	
	-- Place restart buttons
	self.restart:setAnchorPosition(0, 0)
	self.restart:setPosition(10, 10)
	self.restart:addEventListener(Event.MOUSE_DOWN, restartDown, self)
	self.restart:addEventListener(Event.MOUSE_UP, restartUp, self)
	
	-- Create elastic band for catapult
	local onBirdBandX = self.bird_idle:getX() - self.bird_idle:getWidth() / 2
	local onBirdBandY = self.bird_idle:getHeight() / 2
	self.band_l = Shape.new()
	self.band_l:setFillStyle(Shape.SOLID, 0x382E1C)
	self.band_l:beginPath(Shape.NON_ZERO)
	self.band_l:lineTo(onBirdBandX + 4, self.bird_idle:getY() - onBirdBandY + 5)
	self.band_l:lineTo(onBirdBandX + 4, self.bird_idle:getY() + onBirdBandY - 5)
	self.band_l:lineTo(87, 198)
	self.band_l:lineTo(85, 185)
	self.band_l:closePath()
	self.band_l:endPath()

	self.band_r = Shape.new()
	self.band_r:setFillStyle(Shape.SOLID, 0x382E1C)
	self.band_r:beginPath(Shape.NON_ZERO)
	self.band_r:lineTo(onBirdBandX + 4, self.bird_idle:getY() - onBirdBandY + 5)
	self.band_r:lineTo(onBirdBandX + 4, self.bird_idle:getY() + onBirdBandY - 5)
	self.band_r:lineTo(110, 198)
	self.band_r:lineTo(110, 187)
	self.band_r:closePath()
	self.band_r:endPath()

	-- Add drag events to bird
	self.bird_idle:addEventListener(Event.MOUSE_DOWN, onMouseDown, self)
	self.bird_idle:addEventListener(Event.MOUSE_MOVE, onMouseMove, self)
	self.bird_idle:addEventListener(Event.MOUSE_UP, onMouseUp, self)

	-- Add all elements to scene
	self:addChildAt(bg, 1)
	self:addChildAt(bg1, 2)
	self:addChildAt(self.catapult_r, 3)
	self:addChildAt(self.band_r, 4)
	self:addChildAt(self.bird_idle, 5)
	self:addChildAt(self.catapult_l, 6)
	self:addChildAt(self.band_l, 7)
	self:addChildAt(self.square_prop, 8)
	self:addChildAt(self.triangle_prop, 9)
	self:addChildAt(self.restart, 10)
	
	self:addEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
    self:addEventListener("exitBegin", self.onExitBegin, self)
end
 
function level:wall(x, y, width, height)
	local wall = Shape.new()
	wall:beginPath()
	
	wall:moveTo(-width/2,-height/2)
	wall:lineTo(width/2, -height/2)
	wall:lineTo(width/2, height/2)
	wall:lineTo(-width/2, height/2)
	wall:closePath()
	wall:endPath()
	wall:setPosition(x,y)
	
	local body = self.world:createBody{type = b2.STATIC_BODY}
	body:setPosition(wall:getX(), wall:getY())
	body:setAngle(wall:getRotation() * math.pi/180)
	local poly = b2.PolygonShape.new()
	poly:setAsBox(wall:getWidth()/2, wall:getHeight()/2)
	local fixture = body:createFixture{shape = poly, density = 1.0, 
	friction = 1, restitution = 0}
	wall.body = body
	wall.body.type = "wall"
	
	stage:addChild(wall)
end

function setHalf(arr)
	for i = 1, #arr do
		arr[i]:setScale(.5, .5, .5)
	end
end

function onMouseDown(self, event)
	local bird = self.bird_idle
	if bird:hitTestPoint(event.x, event.y) and bird.isFly ~= true then
		bird.isFocus = true

		bird.x0, bird.y0 = event.x, event.y

		event:stopPropagation()
	end
end

function onMouseMove(self, event)
	-- if sprite touch and move finger, then change position of sprite
	local bird = self.bird_idle
	if bird.isFocus then
		if event.x > 20 and event.x < 140 then
			local dx = event.x - bird.x0
			bird:setX(bird:getX() + dx)
			bird.x0 = event.x
		end
		
		if event.y > 160 and event.y < 265 then
			local dy = event.y - bird.y0
			bird:setY(bird:getY() + dy)
			self.bird_idle.y0 = event.y
		end
		
		local onBirdBandX = self.bird_idle:getX() - self.bird_idle:getWidth() / 2
		local onBirdBandY = self.bird_idle:getHeight() / 2
		self.band_l:clear()
		self.band_l = Shape.new()
		self.band_l:setFillStyle(Shape.SOLID, 0x382E1C)
		self.band_l:beginPath(Shape.NON_ZERO)
		self.band_l:lineTo(onBirdBandX + 4, self.bird_idle:getY() - onBirdBandY + 5)
		self.band_l:lineTo(onBirdBandX + 4, self.bird_idle:getY() + onBirdBandY - 5)
		self.band_l:lineTo(87, 198)
		self.band_l:lineTo(85, 185)
		self.band_l:closePath()
		self.band_l:endPath()
		self:addChild(self.band_l)
	
		self.band_r:clear()
		self.band_r = Shape.new()
		self.band_r:setFillStyle(Shape.SOLID, 0x382E1C)
		self.band_r:beginPath(Shape.NON_ZERO)
		self.band_r:lineTo(onBirdBandX + 4, self.bird_idle:getY() - onBirdBandY + 5)
		self.band_r:lineTo(onBirdBandX + 4, self.bird_idle:getY() + onBirdBandY - 5)
		self.band_r:lineTo(110, 198)
		self.band_r:lineTo(110, 187)
		self.band_r:closePath()
		self.band_r:endPath()
		self:addChildAt(self.band_r, 4)
		
		event:stopPropagation()
	end
end

function onMouseUp(self, event)
	if self.bird_idle.isFocus and self.bird_idle.isFly ~= true then
		self.bird_idle.isFocus = false

		local bird_body = self.world:createBody{type = b2.DYNAMIC_BODY}
		local circle_shape = b2.CircleShape.new(0, 0, self.bird_idle:getWidth() / 2)
		local bird_fixture = bird_body:createFixture{shape = circle_shape, density = 1.0, friction = .5, restitution = 0}
		
		self.bird_idle.body = bird_body
		self.bird_idle.body:setPosition(self.bird_idle:getX() + self.bird_idle:getWidth() / 2, self.bird_idle:getY() + self.bird_idle:getHeight() / 2)
		
		self.bird_idle.body:applyForce((self.start_x - self.bird_idle:getX()) * 8, (self.start_y - self.bird_idle:getY()) * 8, self.bird_idle.body:getWorldCenter())
		
		self.bird_idle.isFly = true
		
		self.band_l:clear()
		self.band_r:clear()
		
		event:stopPropagation()
	end
end

function restartDown (self, event)
	if self.restart:hitTestPoint(event.x, event.y) then
		self.touch = true
		event:stopPropagation()
	end	
end

function restartUp (self, event)
	if self.touch then
		sceneManager:changeScene("level", 1, SceneManager.flipWithFade, easing.outBack)
	end
end

function level:onEnterFrame() 
	self.world:step(1/60, 8, 3)
	
	local screenW = application:getContentWidth()
	local screenH = application:getContentHeight()
	
	local offsetX = 0;
	local offsetY = 0;
	
	if((self.screenW - self.bird_idle:getX()) < screenW/2) then
		offsetX = -self.screenW + screenW 
	elseif(self.bird_idle:getX() >= screenW/2) then
		offsetX = -(self.bird_idle:getX() - screenW/2)
	end
	
	self:setX(offsetX)
	
	if((self.screenH - self.bird_idle:getY()) < screenH/2) then
		offsetY = -self.screenH + screenH 
	elseif(self.bird_idle:getY()>= screenH/2) then
		offsetY = -(self.bird_idle:getY() - screenH/2)
	end
	
	self:setY(offsetY)
	
	
	for i = 1, self:getNumChildren() do
		local sprite = self:getChildAt(i)
		if sprite.body then
			local body = sprite.body
			local bodyX, bodyY = body:getPosition()
			sprite:setPosition(bodyX, bodyY)
			sprite:setRotation(body:getAngle() * 180 / math.pi)
		end
	end
	
	-- Move intarface with camera
	self.restart:setX(-self:getX())
	
	if self.bird_idle:getX() < 0 or self.bird_idle:getX() > self.screenW then

		sceneManager:changeScene("level", 1, SceneManager.flipWithFade, easing.outBack)
	end
end
 
function level:onExitBegin()
    self:removeEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
end



Ну вот и все. Теперь расскажу как тестировать прямо на устройстве «на лету».

Для этого устанавливаем на андроид смартфон из папки Gideros apk файл GiderosAndroidPlayer.apk и запускаем. Теперь в Gideros Studio выбираем наше устройство (смартфон должен быть подключен к тому же Wi-Fi, что и компьютер):



Тут видно, что «stegges» — это наш компьютер, а «HTC One Mini 2» — это наш смартфон, выбираем его. Ну а теперь просто нажимаем кнопку запуска в Gideros Studio и можно начинать играть.

Компилируем проект


Теперь давайте скомпилируем нашу игру для запуска на андроид устройстве. Заходим в пункт меню «File» и выбираем «Export project» (cmd+E), видим следующее окно.



Выбираем «Android» из выпадающего списка или закладок. Выбираем среду для какой будет экспортирован проект, в нашем случае Eclipse. Вводим название пакета.



Нажимаем OK и выбираем папку куда экспортируем проект. Открываем Eclipse, ПКМ в Package Explorer --> Import --> Existing Android Code --> выбираем папку с нашим экспортированным проектом --> Finish --> ПКМ по проекту в Package Explorer --> Android Tools — > Export Signed App… --> выбираем наш ключ для подписи и выбираем папку для нашего апк. Готово. Думаю, объяснил доходчиво. Жду советов по статье и коду. Если будет желание и настроение, то сделаю еще уроки.

Обещанные исходники (zip, Github)
Апкшник проекта
И на всякий случай плеер для Андроид

Комментарии (4)


  1. stepanton
    13.08.2015 12:10
    +1

    Люблю такие статьи. Когда все просто и доходчиво объясняется!


  1. Perkovec
    13.08.2015 12:37

    Залил исходники на Github, обновил «Обещанные исходники» в конце статьи. Держите)


  1. unlying
    13.08.2015 14:44

    Неожиданно)

    От себя добавлю, что сейчас Gideros поддерживает Android, iOS, Windows Phone, Windows 10, Mac OS(на одном из скриншотов можно это увидеть). И сам проект переведён в open-source. Основная core-team community постепенно дорабатывает проект.


  1. Nicknnn
    13.08.2015 16:29
    +5

    Напомнило «Как нарисовать сову».