Добрый день! Сегодня будем делать классическую игру Space Invaders на движке Love2d. Для любителей «кода сразу» окончательную версию игры можно посмотреть на гитхабе. Тем же кому интересен процесс разработки, добро пожаловать под кат.
Здесь я не смогу описать всего, что есть в окончательной версии, это и не интересно и сделает статью бесконечной. Могу сказать, что кроме того, что я разберу здесь, игра содержит разные режимы (пауза, проигрыш, выигрыш), может выводить отладочную информацию (скорость и количество объектов, память, пр.), у Игрока есть жизни и ведётся счёт, существуют разные уровни игры (не сложность, а последовательность). Всё это либо можно посмотреть в коде, либо разработать собственные варианты.
Итак, план работы:
function love.load()
end
function love.keyreleased( key )
end
function love.draw()
end
function love.update( dt )
end
local player = {}
player.position_x = 500
player.position_y = 550
player.speed_x = 300
player.width = 50
player.height = 50
function player.update( dt )
if love.keyboard.isDown( "right" ) and
player.position_x < ( love.graphics.getWidth() - player.width ) then
player.position_x = player.position_x + ( player.speed_x * dt )
end
if love.keyboard.isDown( "left" ) and player.position_x > 0 then
player.position_x = player.position_x - ( player.speed_x * dt )
end
end
function player.draw()
love.graphics.rectangle(
"fill",
player.position_x,
player.position_y,
player.width,
player.height
)
end
return player
local player = require 'player'
function love.draw()
player.draw()
end
function love.update( dt )
player.update( dt )
end
player.position.x < ( love.graphics.getWidth() - player.width )
player.position.x > 0
local invaders = {}
invaders.rows = 5
invaders.columns = 9
invaders.top_left_position_x = 50
invaders.top_left_position_y = 50
invaders.invader_width = 40
invaders.invader_height = 40
invaders.horizontal_distance = 20
invaders.vertical_distance = 30
invaders.current_speed_x = 50
invaders.current_level_invaders = {}
local initial_speed_x = 50
local initial_direction = 'right'
function invaders.new_invader( position_x, position_y )
return { position_x = position_x,
position_y = position_y,
width = invaders.invader_width,
height = invaders.invader_height }
end
function invaders.new_row( row_index )
local row = {}
for col_index=1, invaders.columns - (row_index % 2) do
local new_invader_position_x = invaders.top_left_position_x + invaders.invader_width * (row_index % 2) + (col_index - 1) * (invaders.invader_width + invaders.horizontal_distance)
local new_invader_position_y = invaders.top_left_position_y + (row_index - 1) * (invaders.invader_height + invaders.vertical_distance)
local new_invader = invaders.new_invader( new_invader_position_x, new_invader_position_y )
table.insert( row, new_invader )
end
return row
end
function invaders.construct_level()
invaders.current_speed_x = initial_speed_x
for row_index=1, invaders.rows do
local invaders_row = invaders.new_row( row_index )
table.insert( invaders.current_level_invaders, invaders_row )
end
end
function invaders.draw_invader( single_invader )
love.graphics.rectangle('line',
single_invader.position_x,
single_invader.position_y,
single_invader.width,
single_invader.height )
end
function invaders.draw()
for _, invader_row in pairs( invaders.current_level_invaders ) do
for _, invader in pairs( invader_row ) do
invaders.draw_invader( invader, is_miniboss )
end
end
end
function invaders.update_invader( dt, single_invader )
single_invader.position_x = single_invader.position_x + invaders.current_speed_x * dt
end
function invaders.update( dt )
local invaders_rows = 0
for _, invader_row in pairs( invaders.current_level_invaders ) do
invaders_rows = invaders_rows + 1
end
if invaders_rows == 0 then
invaders.no_more_invaders = true
else
for _, invader_row in pairs( invaders.current_level_invaders ) do
for _, invader in pairs( invader_row ) do
invaders.update_invader( dt, invader )
end
end
end
end
return invaders
...
local invaders = require 'invaders'
function love.load()
invaders.construct_level()
end
function love.draw()
...
invaders.draw()
end
function love.update( dt )
...
invaders.update( dt )
end
for col_index=1, invaders.columns - (row_index % 2) do
local new_invader_position_x = invaders.top_left_position_x + invaders.invader_width * (row_index % 2) + (col_index - 1) * (invaders.invader_width + invaders.horizontal_distance)
for col_index=1, invaders.columns do
local new_invader_position_x = invaders.top_left_position_x + (col_index - 1) * (invaders.invader_width + invaders.horizontal_distance)
Текущий вариант | Прямоугольный вариант |
---|---|
local walls = {}
walls.wall_thickness = 1
walls.bottom_height_gap = 1/5 * love.graphics.getHeight()
walls.current_level_walls = {}
function walls.new_wall( position_x, position_y, width, height )
return { position_x = position_x,
position_y = position_y,
width = width,
height = height }
end
function walls.construct_level()
local left_wall = walls.new_wall( 0,
0,
walls.wall_thickness,
love.graphics.getHeight() - walls.bottom_height_gap
)
local right_wall = walls.new_wall( love.graphics.getWidth() - walls.wall_thickness,
0,
walls.wall_thickness,
love.graphics.getHeight() - walls.bottom_height_gap
)
local top_wall = walls.new_wall( 0,
0,
love.graphics.getWidth(),
walls.wall_thickness
)
local bottom_wall = walls.new_wall( 0,
love.graphics.getHeight() - walls.bottom_height_gap - walls.wall_thickness,
love.graphics.getWidth(),
walls.wall_thickness
)
walls.current_level_walls["left"] = left_wall
walls.current_level_walls["right"] = right_wall
walls.current_level_walls["top"] = top_wall
walls.current_level_walls["bottom"] = bottom_wall
end
function walls.draw_wall(wall)
love.graphics.rectangle( 'line',
wall.position_x,
wall.position_y,
wall.width,
wall.height
)
end
function walls.draw()
for _, wall in pairs( walls.current_level_walls ) do
walls.draw_wall( wall )
end
end
return walls
...
local walls = require 'walls'
function love.load()
...
walls.construct_level()
end
function love.draw()
...
-- walls.draw()
end
local collisions = {}
function collisions.check_rectangles_overlap( a, b )
local overlap = false
if not( a.x + a.width < b.x or b.x + b.width < a.x or
a.y + a.height < b.y or b.y + b.height < a.y ) then
overlap = true
end
return overlap
end
function collisions.invaders_walls_collision( invaders, walls )
local overlap, wall
if invaders.current_speed_x > 0 then
wall, wall_type = walls.current_level_walls['right'], 'right'
else
wall, wall_type = walls.current_level_walls['left'], 'left'
end
local a = { x = wall.position_x,
y = wall.position_y,
width = wall.width,
height = wall.height }
for _, invader_row in pairs( invaders.current_level_invaders ) do
for _, invader in pairs( invader_row ) do
local b = { x = invader.position_x,
y = invader.position_y,
width = invader.width,
height = invader.height }
overlap = collisions.check_rectangles_overlap( a, b )
if overlap then
if wall_type == invaders.allow_overlap_direction then
invaders.current_speed_x = -invaders.current_speed_x
if invaders.allow_overlap_direction == 'right' then
invaders.allow_overlap_direction = 'left'
else
invaders.allow_overlap_direction = 'right'
end
invaders.descend_by_row()
end
end
end
end
end
function collisions.resolve_collisions( invaders, walls )
collisions.invaders_walls_collision( invaders, walls )
end
return collisions
invaders.allow_overlap_direction = 'right'
function invaders.descend_by_row_invader( single_invader )
single_invader.position_y = single_invader.position_y + invaders.vertical_distance / 2
end
function invaders.descend_by_row()
for _, invader_row in pairs( invaders.current_level_invaders ) do
for _, invader in pairs( invader_row ) do
invaders.descend_by_row_invader( invader )
end
end
end
local collisions = require 'collisions'
function love.update( dt )
...
collisions.resolve_collisions( invaders, walls )
end
if overlap then
if wall_type == invaders.allow_overlap_direction then
...
local bullets = {}
bullets.current_speed_y = -200
bullets.width = 2
bullets.height = 10
bullets.current_level_bullets = {}
function bullets.destroy_bullet( bullet_i )
bullets.current_level_bullets[bullet_i] = nil
end
function bullets.new_bullet(position_x, position_y)
return { position_x = position_x,
position_y = position_y,
width = bullets.width,
height = bullets.height }
end
function bullets.fire( player )
local position_x = player.position_x + player.width / 2
local position_y = player.position_y
local new_bullet = bullets.new_bullet( position_x, position_y )
table.insert(bullets.current_level_bullets, new_bullet)
end
function bullets.draw_bullet( bullet )
love.graphics.rectangle( 'fill',
bullet.position_x,
bullet.position_y,
bullet.width,
bullet.height
)
end
function bullets.draw()
for _, bullet in pairs(bullets.current_level_bullets) do
bullets.draw_bullet( bullet )
end
end
function bullets.update_bullet( dt, bullet )
bullet.position_y = bullet.position_y + bullets.current_speed_y * dt
end
function bullets.update( dt )
for _, bullet in pairs(bullets.current_level_bullets) do
bullets.update_bullet( dt, bullet )
end
end
return bullets
function collisions.invaders_bullets_collision( invaders, bullets )
local overlap
for b_i, bullet in pairs( bullets.current_level_bullets) do
local a = { x = bullet.position_x,
y = bullet.position_y,
width = bullet.width,
height = bullet.height }
for i_i, invader_row in pairs( invaders.current_level_invaders ) do
for i_j, invader in pairs( invader_row ) do
local b = { x = invader.position_x,
y = invader.position_y,
width = invader.width,
height = invader.height }
overlap = collisions.check_rectangles_overlap( a, b )
if overlap then
invaders.destroy_invader( i_i, i_j )
bullets.destroy_bullet( b_i )
end
end
end
end
end
function collisions.bullets_walls_collision( bullets, walls )
local overlap
local wall = walls.current_level_walls['top']
local a = { x = wall.position_x,
y = wall.position_y,
width = wall.width,
height = wall.height }
for b_i, bullet in pairs( bullets.current_level_bullets) do
local b = { x = bullet.position_x,
y = bullet.position_y,
width = bullet.width,
height = bullet.height }
overlap = collisions.check_rectangles_overlap( a, b )
if overlap then
bullets.destroy_bullet( b_i )
end
end
end
function collisions.resolve_collisions( invaders, walls, bullets )
...
collisions.invaders_bullets_collision( invaders, bullets )
collisions.bullets_walls_collision( bullets, walls )
end
...
invaders.speed_x_increase_on_destroying = 10
function invaders.destroy_invader( row, invader )
invaders.current_level_invaders[row][invader] = nil
local invaders_row_count = 0
for _, invader in pairs( invaders.current_level_invaders[row] ) do
invaders_row_count = invaders_row_count + 1
end
if invaders_row_count == 0 then
invaders.current_level_invaders[row] = nil
end
if invaders.allow_overlap_direction == 'right' then
invaders.current_speed_x = invaders.current_speed_x + invaders.speed_x_increase_on_destroying
else
invaders.current_speed_x = invaders.current_speed_x - invaders.speed_x_increase_on_destroying
end
end
...
...
local bullets = require 'bullets'
function love.keyreleased( key )
if key == 'space' then
bullets.fire( player )
end
end
function love.draw()
...
bullets.draw()
end
function love.update( dt )
...
collisions.resolve_collisions( invaders, walls, bullets )
bullets.update( dt )
end
git submodule update --init
...
player.image = love.graphics.newImage('images/Hero.png')
-- from https://love2d.org/forums/viewtopic.php?t=79756
function getImageScaleForNewDimensions( image, newWidth, newHeight )
local currentWidth, currentHeight = image:getDimensions()
return ( newWidth / currentWidth ), ( newHeight / currentHeight )
end
local scaleX, scaleY = getImageScaleForNewDimensions( player.image, player.width, player.height )
function player.draw() -- меняем полностью
love.graphics.draw(player.image,
player.position_x,
player.position_y, rotation, scaleX, scaleY )
end
...
...
invaders.images = {love.graphics.newImage('images/bad_1.png'),
love.graphics.newImage('images/bad_2.png'),
love.graphics.newImage('images/bad_3.png')
}
-- from https://love2d.org/forums/viewtopic.php?t=79756
function getImageScaleForNewDimensions( image, newWidth, newHeight )
local currentWidth, currentHeight = image:getDimensions()
return ( newWidth / currentWidth ), ( newHeight / currentHeight )
end
local scaleX, scaleY = getImageScaleForNewDimensions( invaders.images[1], invaders.invader_width,
invaders.invader_height )
function invaders.new_invader(position_x, position_y ) -- меняем
local invader_image_no = math.random(1, #invaders.images)
invader_image = invaders.images[invader_image_no]
return ({position_x = position_x,
position_y = position_y,
width = invaders.invader_width,
height = invaders.invader_height,
image = invader_image})
end
function invaders.draw_invader( single_invader ) -- меняем
love.graphics.draw(single_invader.image,
single_invader.position_x,
single_invader.position_y, rotation, scaleX, scaleY )
end
function love.load()
...
math.randomseed( os.time() )
...
end
К сожалению, не доступен сервер mySQL