Programming Fundamentals
Lecture 06 Arrays, Matrices, Animations and
World Representations
Edirlei Soares de Lima
Arrays
Arrays are sequences of items (like variables) of the same
type.
Each item is identified by an index (integer).
With arrays we can store in memory sequences of values
numbers, text, imagens, etc.), which are all associated with a
single variable (the array).
(
Arrays in Lua
In Lua, are implemented through tables indexed by integer
numbers.
Different from many other programming languages, in Lua we
don't need to define the maximum size of an array.
Creating a new array:
my_array = {}
Arrays in Lua
Initializing some positions of the array:
my_array[1] = 5
my_array[2] = 11
my_array[5] = 0
my_array[10] = 3
Table Functions
Size of an array:
arr = {1, 2, 1, 6}
size = #arr
arr = {1, 2, 1, 6}
table.insert(arr, 8)
table.insert(arr, 1, 10)
Remove elements:
arr = {1, 2, 1, 6}
table.remove(arr, 4)
table.remove(arr, 1)
Example 1: Platforms, Arrays and Camera
require "vector2"
world.lua
function CreateObject(x, y, w, h)
return {position = vector2.new(x, y), size = vector2.new(w, h)}
end
function DrawWorld(world)
for i = 1, table.getn(world), 1 do
love.graphics.rectangle("fill", world[i].position.x,
world[i].position.y, world[i].size.x, world[i].size.y)
end
end
function GetBoxCollisionDirection(x1,y1,w1,h1,x2,y2,w2,h2)
local xdist = math.abs((x1 + (w1 / 2)) - (x2 + (w2 / 2)))
local ydist = math.abs((y1 + (h1 / 2)) - (y2 + (h2 / 2)))
collision.lua
local combinedwidth = (w1 / 2) + (w2 / 2)
local combinedheight = (h1 / 2) + (h2 / 2)
...
Example 1: Platforms, Arrays and Camera
if(xdist > combinedwidth) then
return vector2.new(0, 0)
end
collision.lua
if(ydist > combinedheight) then
return vector2.new(0, 0)
end
local overlapx = math.abs(xdist - combinedwidth)
local overlapy = math.abs(ydist - combinedheight)
local playerdir = vector2.normalize(vector2.sub(vector2.new(x1,y1),
vector2.new(x2,y2)))
local collisiondir
if overlapx > overlapy then
collisiondir = vector2.normalize(vector2.new(0, playerdir.y *
overlapy))
elseif overlapx < overlapy then
collisiondir = vector2.normalize(vector2.new(playerdir.x *
overlapx, 0))
else
collisiondir = vector2.normalize(vector2.new(playerdir.x *
overlapx, playerdir.y * overlapy))
end
return collisiondir
end
Example 1: Platforms, Arrays and Camera
require "vector2"
require "collision"
player.lua
local player = {
position = vector2.new(400, 100),
velocity = vector2.new(0, 0),
size = vector2.new(30, 60),
maxspeed = 400,
mass = 1,
frictioncoefficient = 300,
onGround = false
}
function UpdatePlayer(dt, world)
local acceleration = vector2.new(0, 0)
local gravity = vector2.new(0, 500)
acceleration = vector2.applyForce(gravity, player.mass,
acceleration)
...
Example 1: Platforms, Arrays and Camera
local friction = vector2.mult(player.velocity, -1)
player.lua
friction = vector2.normalize(friction)
friction = vector2.mult(friction, player.frictioncoefficient)
acceleration = vector2.applyForce(friction, player.mass,
acceleration)
local movedirection = vector2.new(0, -1)
if love.keyboard.isDown("right") then
local move = vector2.new(500, 0)
acceleration = vector2.applyForce(move, player.mass, acceleration)
movedirection.x = 1
end
if love.keyboard.isDown("left") then
local move = vector2.new(-500, 0)
acceleration = vector2.applyForce(move, player.mass, acceleration)
movedirection.x = -1
end
if love.keyboard.isDown("up") and (player.onGround) then
player.velocity.y = -450
movedirection.y = 1
player.onGround = false
end
Example 1: Platforms, Arrays and Camera
player.lua
vector2.mult(acceleration, dt))
futurevelocity = vector2.limit(futurevelocity, player.maxspeed)
vector2.mult(futurevelocity, dt))
acceleration = CheckCollision(world, futureposition,
movedirection, acceleration)
vector2.mult(acceleration, dt))
player.velocity = vector2.limit(player.velocity, player.maxspeed)
vector2.mult(player.velocity, dt))
end
function DrawPlayer()
love.graphics.setColor(0.2, 0.2, 0.9)
love.graphics.rectangle("fill", 380, player.position.y,
player.size.x, player.size.y)
end
Example 1: Platforms, Arrays and Camera
function CheckCollision(world, futureposition, movedirection,
player.lua
acceleration)
for i = 1, table.getn(world), 1 do
local collisiondir = GetBoxCollisionDirection(futureposition.x,
futureposition.y, player.size.x, player.size.y,
world[i].position.x, world[i].position.y,
world[i].size.x, world[i].size.y)
if not (collisiondir.x == 0 and collisiondir.y == 0) then
if collisiondir.y == movedirection.y then --down collision
player.velocity.y = 0
acceleration.y = 0
player.onGround = true
elseif collisiondir.y == 1 then --up collision
player.velocity.y = 0
acceleration.y = 0
elseif movedirection.x ~= collisiondir.x then --side collision
player.velocity.x = 0
acceleration.x = 0
end
end
end
return acceleration
end
Example 1: Platforms, Arrays and Camera
function GetPlayerPosition()
return player.position
end
player.lua
require "vector2"
require "world"
require "player"
main.lua
local world = {}
world[1] = CreateObject(50, 550, 1200, 50)
world[2] = CreateObject(500, 450, 50, 50)
world[3] = CreateObject(750, 500, 100, 50)
world[4] = CreateObject(1000, 420, 150, 50)
end
function love.update(dt)
UpdatePlayer(dt, world)
end
Example 1: Platforms, Arrays and Camera
function love.draw()
love.graphics.setColor(0.2, 0.2, 0.9)
DrawPlayer()
main.lua
love.graphics.setColor(0.2, 0.8, 0.2)
local playerpos = GetPlayerPosition()
love.graphics.translate(-(playerpos.x - 380), 0)
DrawWorld(world)
end
Example 2: Arrays and Enemies
require "vector2"
enemy.lua
function CreateEnemy(x, y, r, t)
local mdir, vdir
if t == 1 then
mdir = vector2.new(1, 0)
vdir = vector2.new(-100, 0)
elseif t == 2 then
mdir = vector2.new(0, -1)
vdir = vector2.new(0, 100)
end
return {position = vector2.new(x, y),
velocity = vdir,
etype = t,
mass = 1,
movedirection = mdir,
movechangetime = 2,
movetimer = 0}
end
Example 2: Arrays and Enemies
function UpdateEnemies(dt, enemies)
for i = 1, table.getn(enemies), 1 do
enemy.lua
local acceleration = vector2.new(0, 0)
if enemies[i].etype == 1 or enemies[i].etype == 2 then
local moveForce = vector2.mult(enemies[i].movedirection, 100)
acceleration = vector2.applyForce(moveForce, enemies[i].mass,
acceleration)
vector2.mult(acceleration, dt))
enemies[i].velocity = vector2.limit(enemies[i].velocity, 100)
vector2.mult(enemies[i].velocity, dt))
enemies[i].movetimer = enemies[i].movetimer + dt
if enemies[i].movetimer > enemies[i].movechangetime then
enemies[i].movetimer = 0
enemies[i].movedirection =
vector2.mult(enemies[i].movedirection, -1)
end
end
end
end
Example 2: Arrays and Enemies
function DrawEnemies(enemies)
for i = 1, table.getn(enemies), 1 do
if enemies[i].etype == 1 then
enemy.lua
love.graphics.setColor(0.8, 0.0, 0.0)
elseif enemies[i].etype == 2 then
love.graphics.setColor(0.8, 0.8, 0.0)
end
love.graphics.circle("fill", enemies[i].position.x,
end
end
require "vector2"
require "world"
require "player"
require "enemy"
main.lua
local world = {}
local enemies = {}
Example 2: Arrays and Enemies
enemy.lua
world[1] = CreateObject(50, 550, 1200, 50)
world[2] = CreateObject(500, 450, 50, 50)
world[3] = CreateObject(750, 500, 100, 50)
world[4] = CreateObject(1000, 420, 150, 50)
enemies[1] = CreateEnemy(650, 500, 20, 1)
enemies[2] = CreateEnemy(1100, 350, 20, 2)
end
function love.update(dt)
UpdatePlayer(dt, world)
UpdateEnemies(dt, enemies)
End
function love.draw()
love.graphics.setColor(0.2, 0.2, 0.9)
DrawPlayer()
love.graphics.setColor(0.2, 0.8, 0.2)
local playerpos = GetPlayerPosition()
love.graphics.translate(-(playerpos.x - 380), 0)
DrawWorld(world)
DrawEnemies(enemies)
end
Arrays and Animations
Animations are created by sequences of images.
Example:
We can store animations as arrays of images!
require "vector2"
local hero = {
walk = {}, -- array of images
anim_frame = 1,
position = vector2.new(100, 225),
velocity = vector2.new(0, 0)
}
for i = 1, 4, 1 do -- load the animation frames
hero.walk[i] = love.graphics.newImage("Hero_Walk_0" .. i .. ".png")
end
end
function love.draw() -- draw the character using the animation index
love.graphics.draw(hero.walk[hero.anim_frame], hero.position.x,
hero.position.y)
end
...
...
function love.update(dt)
if love.keyboard.isDown("right") then
hero.velocity = vector2.new(100, 0)
hero.anim_frame = hero.anim_frame + 1 -- increases the anim. index
if hero.anim_frame > 4 then
hero.anim_frame = 1
end
-- animation loop
else
hero.velocity = vector2.new(0, 0)
end
vector2.mult(hero.velocity, dt))
end
Example of Animation
Problem: we did not control the speed of the animation!
The faster the computer, the faster the animation will be played.
function love.update(dt)
if love.keyboard.isDown("right") then
hero.velocity = vector2.new(100, 0)
hero.anim_timer = hero.anim_timer + dt -- increases the time with dt
if hero.anim_timer > 0.1 then
-- when time gets to 0.1
hero.anim_frame = hero.anim_frame + 1 -- increases the anim. index
if hero.anim_frame > 4 then
hero.anim_frame = 1
end
-- animation loop
hero.anim_timer = 0
-- reset the time counter
end
else
hero.velocity = vector2.new(0, 0)
end
vector2.mult(hero.velocity, dt))
end
Exercise 1
) Continue the implementation of the last exercise by adding the
animations and movement of the character to all other
directions. Use the following images:
1
Texture Atlas
A texture atlas (also called a
sprite sheet or an image sprite) is
an image containing a collection
of smaller images, usually packed
together to reduce the atlas size.
It is often more efficient to store
the textures in a texture atlas
which is treated as a single unit
by the graphics hardware.
https://edirlei.com/aulas/gameprog/spritesheet_example.png
require "vector2"
local hero = {
position = vector2.new(100, 225),
velocity = vector2.new(0, 0),
spritesheet = love.graphics.newImage("spritesheet_example.png"),
anim_frame = 1,
anim_timer = 0
}
function CreateSpriteSheetQuads(spritesheet, cols, rows, w, h)
local count = 1
for j = 0, rows - 1, 1 do
for i = 0, cols - 1, 1 do
spritesheet:getWidth(), spritesheet:getHeight())
count = count + 1
end
end
end
...
6
4, 64)
end
function love.draw()
love.graphics.draw(hero.spritesheet,
hero.position.x, hero.position.y)
end
function love.update(dt)
if love.keyboard.isDown("down") then
hero.velocity = vector2.new(0, 100)
hero.anim_timer = hero.anim_timer + dt
if hero.anim_timer > 0.1 then
hero.anim_frame = hero.anim_frame + 1
if hero.anim_frame > 4 then
hero.anim_frame = 1
end
hero.anim_timer = 0
end
...
...
elseif love.keyboard.isDown("right") then
hero.velocity = vector2.new(100, 0)
hero.anim_timer = hero.anim_timer + dt
if hero.anim_timer > 0.1 then
if hero.anim_frame < 9 or hero.anim_frame > 12 then
hero.anim_frame = 9
end
hero.anim_frame = hero.anim_frame + 1
if hero.anim_frame >= 12 then
hero.anim_frame = 9
end
hero.anim_timer = 0
end
else
hero.velocity = vector2.new(0, 0)
end
vector2.mult(hero.velocity, dt))
end
Matrices
Matrices are two-dimensional arrays.
A matrix stores data in an organized form with rows and
columns.
3
7
1
5
6
1 8 6 1
2 5 4 9
9 3 1 2
8 6 7 3
4 9 2 1
Matrices in Lua
Declaring and initializing a matrix:
my_matrix = {}
-- new matrix
for i=1, 10, 1 do
my_matrix[i] = {} -- new row
for j=1, 10, 1 do
my_matrix[i][j] = 0
end
end
We are defining and initializing a matrix with 10 columns e 10 rows.
Matrices in Lua
We can access the values stored in the matrix using their two-
dimensional indexes.
1
2 3
1
5
? ?1
?
?
?
? ?
?8 ?
2
3
my_matrix[1][1] = 5;
my_matrix[2][3] = 8;
my_matrix[3][1] = 1;
Matrices and Game Worlds
We can use matrices to represent 2D game worlds.
Example:
Matrices and Game Worlds Example 1
Generating a random matrix and drawing its elements on
screen using colors.
local world = {}
for i=1, 26, 1 do -- initializes a random matrix (26 x 20)
world[i] = {}
for j=1, 20, 1 do
world[i][j] = love.math.random(0, 3)
end
end
end
function love.draw()
for i=1, 26, 1 do -- iterates through the matrix and draw the elements
for j=1, 20, 1 do
if (world[i][j] == 0) then
love.graphics.setColor(1, 0, 0)
elseif (world[i][j] == 1) then
love.graphics.setColor(0, 1, 0)
elseif (world[i][j] == 2) then
love.graphics.setColor(0, 0, 1)
elseif (world[i][j] == 3) then
love.graphics.setColor(1, 1, 0)
end
love.graphics.rectangle("fill", (i * 30)-20, (j * 30)-30, 30, 30)
end
end
end
Matrices and Game Worlds Example 1
Matrices and Game Worlds Example 2
Reading a matrix from a file and drawing its elements
on screen using colors.
World1.txt
GGGGGGAGGGGGGG
GGGGGGAGGGGGGG
GGGGGGAGGGGGGG
GGGGGGAGGGGGGG
GGGGGGAAAGGGGG
GGGGGGGGAGGGGG
GGGGGGGGAGGGGG
PPPPPPPPAPPPPP
AAAAAAAAAAAAAA
AAAAAAAAAAAAAA
Matrices and Game Worlds Example 2
local world = {}
- reads the content of the file
local file = io.open(filename)
local i = 1
for line in file:lines() do
world[i] = {}
for j=1, #line, 1 do
world[i][j] = line:sub(j,j)
end
i = i + 1
end
file:close()
end
end
.
.
.
Matrices and Game Worlds Example 2
.
.
.
function love.draw()
for i=1, 10, 1 do -- iterates through the matrix and draw the elements
for j=1, 14, 1 do
if (world[i][j] == "P") then
love.graphics.setColor(0.901, 0.921, 0.525)
elseif (world[i][j] == "G") then
love.graphics.setColor(0.149, 0.6, 0)
elseif (world[i][j] == "A") then
love.graphics.setColor(0.250, 0.490, 0.909)
end
love.graphics.rectangle("fill", (j * 50), (i * 50), 50, 50)
end
end
end
Matrices and Game Worlds Example 2
Matrices and Game Worlds Example 3
Reading a matrix from a file and drawing its elements
on screen using images.
World1.txt
Images:
GGGGGGAGGGGGGG
GGGGGGAGGGGGGG
GGGGGGAGGGGGGG
GGGGGGAGGGGGGG
GGGGGGAAAGGGGG
GGGGGGGGAGGGGG
GGGGGGGGAGGGGG
PPPPPPPPAPPPPP
AAAAAAAAAAAAAA
AAAAAAAAAAAAAA
Matrices and Game Worlds Example 3
local world = {}
local tile_grass
local tile_water
local tile_sand
local file = io.open(filename)
local i = 1
for line in file:lines() do
world[i] = {}
for j=1, #line, 1 do
world[i][j] = line:sub(j,j)
end
i = i + 1
end
file:close()
end
.
.
.
.
.
.
tile_grass = love.graphics.newImage("grass.png")
tile_water = love.graphics.newImage("water.png")
tile_sand = love.graphics.newImage("sand.png")
end
function love.draw()
for i=1, 10, 1 do -- iterates through the matrix and draw the elements
for j=1, 14, 1 do
if (world[i][j] == "P") then
love.graphics.draw(tile_sand, (j * 50), (i * 50))
elseif (world[i][j] == "G") then
love.graphics.draw(tile_grass, (j * 50), (i * 50))
elseif (world[i][j] == "A") then
love.graphics.draw(tile_water, (j * 50), (i * 50))
end
end
end
end
Matrices and Game Worlds Example 3
Exercise 2
) Implement a program to display the world of a platform game,
which is stored in a text file.
2
Exercise 3
) Continue the implementation of the last exercise and code a
virtual camera to allow the player to move and see the entire
environment.
3
Important: don’t draw parts of world that are not visible in the screen.
Texture Atlas
A texture atlas (also called a
sprite sheet or an image sprite) is
an image containing a collection
of smaller images, usually packed
together to reduce the atlas size.
It is often more efficient to store
the textures in a texture atlas
which is treated as a single unit
by the graphics hardware.
Texture Atlas Example
World
Tileset
XXXXXXXXXXXXXX
XXXXXXXXXXXXXX
XXXXXXXXXXXXXX
XXXXXXXXXXXXXX
XXXXXXXXXXXXXX
XXXBXXXXXXXXXX
XXBBBXXXXXXXXX
GGGGGGGAAGGGGG
TTTTTTTPPTTTTT
TTTTTTTPPTTTTT
http://www.inf.puc-rio.br/~elima/intro-eng/plataform_map1.zip
Texture Atlas Example
local world = {}
local tilesetImage
local tileSize = 64
tilesetImage = love.graphics.newImage(filename)
local count = 1
for i = 0, nx, 1 do
for j = 0, ny, 1 do
j * tileSize, tileSize, tileSize,
tilesetImage:getWidth(),
tilesetImage:getHeight())
count = count + 1
end
end
end
.
.
.
.
.
.
local file = io.open(filename)
local i = 1
for line in file:lines() do
world[i] = {}
for j=1, #line, 1 do
world[i][j] = line:sub(j,j)
end
i = i + 1
end
file:close()
end
love.graphics.setBackgroundColor(0.6, 0.819, 0.980)
end
.
.
.
.
.
.
function love.draw()
for i=1, 10, 1 do
for j=1, 14, 1 do
if (world[i][j] == "G") then
(j * tileSize) - tileSize, (i * tileSize) - tileSize)
elseif (world[i][j] == "T") then
(j * tileSize) - tileSize, (i * tileSize) - tileSize)
elseif (world[i][j] == "A") then
(j * tileSize) - tileSize, (i * tileSize) - tileSize)
elseif (world[i][j] == "P") then
(j * tileSize) - tileSize, (i * tileSize) - tileSize)
elseif (world[i][j] == "B") then
(j * tileSize) - tileSize, (i * tileSize) - tileSize)
end
end
end
end
Texture Atlas Example
Tile-Based Scrolling Example
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXBXXXXXXXXXXXXXBXXXXXXXXXX
XXBBBXXXXXXXXXXXBBBXXXXXXXXX
GGGGGGGAAGGGGGGGGGGGGAAGGGGG
TTTTTTTPPTTTTTTTTTTTTPPTTTTT
TTTTTTTPPTTTTTTTTTTTTPPTTTTT
http://www.inf.puc-rio.br/~elima/intro-eng/plataform_map2.zip
Tile-Based Scrolling Example
local world = {}
local tilesetImage
local tileSize = 64
local world_config = {
worldSize_x = 28,
worldSize_y = 10,
worldDisplay_x = 14,
worldDisplay_y = 10
}
local camera = {
pos_x = 1,
pos_y = 1,
speed = 120
}
tilesetImage = love.graphics.newImage(filename)
local count = 1
for i = 0, nx, 1 do
for j = 0, ny, 1 do
tileSize, tileSize, tileSize,
tilesetImage:getWidth(),
tilesetImage:getHeight())
count = count + 1
end
end
end
local file = io.open(filename)
local i = 1
for line in file:lines() do
world[i] = {}
for j=1, #line, 1 do
world[i][j] = line:sub(j,j)
end
i = i + 1
end
file:close()
end
love.graphics.setBackgroundColor(0.6, 0.819, 0.980)
end
function love.update(dt)
if love.keyboard.isDown("right") then
camera.pos_x = camera.pos_x + (camera.speed * dt)
elseif love.keyboard.isDown("left") then
camera.pos_x = camera.pos_x - (camera.speed * dt)
end
if camera.pos_x < 0 then
camera.pos_x = 0
elseif camera.pos_x > world_config.worldSize_x * tileSize -
world_config.worldDisplay_x * tileSize - 1 then
camera.pos_x = world_config.worldSize_x * tileSize -
world_config.worldDisplay_x * tileSize - 1
end
end
function love.draw()
offset_x = math.floor(camera.pos_x % tileSize)
first_tile_x = math.floor(camera.pos_x / tileSize)
for y=1, world_config.worldDisplay_y, 1 do
for x=1, world_config.worldDisplay_x, 1 do
if (world[y][first_tile_x + x] == "G") then
((x -1)*tileSize) - offset_x ,((y-1)*tileSize))
elseif (world[y][first_tile_x + x] == "T") then
((x-1)*tileSize) - offset_x ,((y-1)*tileSize))
elseif (world[y][first_tile_x + x] == "A") then