Difference between revisions of "2D Space Shooter Part 2: Background"
(12 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
+ | __TOC__ | ||
== A bit of set up == | == A bit of set up == | ||
− | Let's begin by setting up | + | [[File:2D Spaceshooter Star far.jpg|thumb|upright=0.5|Our starry background]] |
+ | Let's begin by setting up some folder. | ||
+ | |||
+ | We want a scrolling starry background, so let's add it to the project: | ||
+ | * Create a folder named 'gfx' under 'Files' in your gideros project | ||
+ | * Download the '''[[Media:2D Spaceshooter Star far.jpg|starry background texture]]''' (not the thumbnail on the right) on your computer, rename it to ''''star_far.jpg'''', and add it to your project under the 'gfx' folder you just created | ||
− | |||
− | |||
Because it is repeatable, its dimensions must be a power of two. I choosed a width of 512 for the texture, so let's configure our project according to that width. | Because it is repeatable, its dimensions must be a power of two. I choosed a width of 512 for the texture, so let's configure our project according to that width. | ||
− | In your project settings (create a new project for this tutorial if not already done!), go the 'Graphics' tab and set: | + | In your project settings (create a new project for this tutorial if not already done!), go to the 'Graphics' tab and set: |
* Scale mode to 'Fit Width' | * Scale mode to 'Fit Width' | ||
* Logical dimensions to 512 x 1024 | * Logical dimensions to 512 x 1024 | ||
* Orientation to portrait | * Orientation to portrait | ||
− | This will ensure that whatever the phone size is, our canvas will have a width of 512. The height doesn't matter actually, you can use anything instead of 1024 but it is convenient to specify something sensible, that is taller than | + | This will ensure that whatever the phone size is, our canvas will have a width of 512. The height doesn't matter actually, you can use anything instead of 1024 but it is convenient to specify something sensible, that is taller than the width, since we want our game to be in portrait. |
However we'll need to figure out the actual height and logical position of our screen at run time, in order to place our graphics on the stage. | However we'll need to figure out the actual height and logical position of our screen at run time, in order to place our graphics on the stage. | ||
− | The code below will do what we want | + | The code below will do what we want. Create a '''main.lua''' file and add the following code: |
− | < | + | <syntaxhighlight lang="lua"> |
− | -- | + | -- compute screen bounds |
− | + | SCR_LEFT, SCR_TOP, SCR_RIGHT, SCR_BOTTOM = application:getLogicalBounds() | |
− | </ | + | </syntaxhighlight> |
+ | |||
+ | Here we ask Gideros for the actual screen bounds in canvas space, and store them in global variables so that they can be accessed from anywhere in the code. They will be useful for positionning objects, but also for checking if objects go out of bounds. | ||
− | Next step is to create our background and put it on stage. To keep our code clean, let's handle the background image as an object of class Background. We'll define it | + | To avoid confusion, I'll always declare my global variables in upper case, but of course you are free to use another convention. |
− | < | + | |
− | -- | + | Next step is to create our background and put it on stage. To keep our code clean, let's handle the background image as an object of class Background. We'll define it later. |
− | local background=Background.new( | + | <syntaxhighlight lang="lua"> |
− | -- | + | -- create the background object with the screen size |
+ | -- width should be 512 per project settings, but we'll compute it anyway to avoid relying on constants | ||
+ | local background = Background.new(SCR_RIGHT-SCR_LEFT, SCR_BOTTOM-SCR_TOP) | ||
+ | -- add it to stage | ||
stage:addChild(background) | stage:addChild(background) | ||
− | background:setY( | + | background:setY(SCR_TOP) |
− | </ | + | </syntaxhighlight> |
Our background will move as our spaceship progress through the game. We'll make it 'advance' in our game loop: | Our background will move as our spaceship progress through the game. We'll make it 'advance' in our game loop: | ||
− | < | + | <syntaxhighlight lang="lua"> |
− | -- | + | -- this is our game loop |
− | stage:addEventListener(Event.ENTER_FRAME,function () | + | stage:addEventListener(Event.ENTER_FRAME, function() |
background:advance(1) | background:advance(1) | ||
end) | end) | ||
− | </ | + | </syntaxhighlight> |
− | The code of our game loop will be called on each animation frame, 60 times per second. In it, we call the 'advance' method of the background object with a parameter telling it by how much we want to | + | The code of our game loop will be called on each animation frame, 60 times per second. In it, we call the 'advance' method of the background object with a parameter telling it by how much we want to move. This will allow us to alter game speed later if we ever need to. |
So far our main.lua contains: | So far our main.lua contains: | ||
− | < | + | <syntaxhighlight lang="lua"> |
− | -- | + | -- compute screen bounds |
− | + | SCR_LEFT, SCR_TOP, SCR_RIGHT, SCR_BOTTOM = application:getLogicalBounds() | |
− | -- | + | -- create the background object with the screen size |
− | local background=Background.new( | + | -- width should be 512 per project settings, but we'll compute it anyway to avoid relying on constants |
− | -- | + | local background = Background.new(SCR_RIGHT-SCR_LEFT, SCR_BOTTOM-SCR_TOP) |
+ | -- add it to stage | ||
stage:addChild(background) | stage:addChild(background) | ||
− | background:setY( | + | background:setY(SCR_TOP) |
− | -- | + | -- this is our game loop |
− | stage:addEventListener(Event.ENTER_FRAME,function () | + | stage:addEventListener(Event.ENTER_FRAME, function() |
background:advance(1) | background:advance(1) | ||
end) | end) | ||
− | </ | + | </syntaxhighlight> |
− | You can try to launch your project, but | + | You can try to launch your project, but it won't run yet because we didn't define the 'Background' class. |
== A basic background object == | == A basic background object == | ||
− | Create a new lua file and call it 'background.lua'. In this file, we'll define the 'Background' class. | + | Create a new lua file and call it '''background.lua'''. In this file, we'll define the 'Background' class. |
+ | |||
Our background object will mostly display an image, so let our Background class be a subclass of Gideros Pixel object: | Our background object will mostly display an image, so let our Background class be a subclass of Gideros Pixel object: | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | -- our background will be a subclass of Gideros Pixel | ||
+ | -- here we pass an empty constructor function so that the Pixel is initialized with defaults | ||
+ | Background = Core.class(Pixel, function() end) | ||
+ | </syntaxhighlight> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
The second argument to the Core.class function is used to specify the parameters to use when constructing the 'Pixel' we inherit from. Here this is just an empty function, meaning that we don't pass any parameters to the Pixel.new that will be called internally. | The second argument to the Core.class function is used to specify the parameters to use when constructing the 'Pixel' we inherit from. Here this is just an empty function, meaning that we don't pass any parameters to the Pixel.new that will be called internally. | ||
Now let's add empty functions for our class so that our code runs. | Now let's add empty functions for our class so that our code runs. | ||
− | < | + | <syntaxhighlight lang="lua"> |
− | -- | + | -- initialize our background with the given width and height |
− | function Background:init(w,h) | + | function Background:init(w, h) |
end | end | ||
function Background:advance(amount) | function Background:advance(amount) | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
The 'init' method will be called by main.lua during 'Background.new', while the 'advance' method is called from our main loop. If you start a Gideros Player and hit play, our code will run, but won't show anything. | The 'init' method will be called by main.lua during 'Background.new', while the 'advance' method is called from our main loop. If you start a Gideros Player and hit play, our code will run, but won't show anything. | ||
− | Let's add some code to: | + | Let's add some code to load our texture and attach it to our background: |
− | + | <syntaxhighlight lang="lua"> | |
− | + | function Background:init(w, h) | |
+ | local far = Texture.new("gfx/star_far.jpg", true, { wrap=TextureBase.REPEAT }) | ||
+ | -- set up the far view | ||
+ | self:setTexture(far) | ||
+ | self:setDimensions(w, h) | ||
+ | end | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | This tells Gideros to load our image called 'star_far.jpg' from the 'gfx' folder of our project structure, and make it a texture. We also ask Gideros to enable filtering ('true') and that the texture should repeat the image ('{ wrap=TextureBase.REPEAT }'). | ||
+ | We then assign this texture to our background object. Remember that Background is a subclass of Pixel, so it has all the properties and methods of a Pixel object. | ||
+ | |||
+ | The final line changes the size of the background to the dimensions given in arguments. | ||
+ | |||
+ | Our project should now run and show a space background. | ||
+ | |||
+ | == A nicer background == | ||
+ | Something is shown, but it doesn't look quite good for a space shooter game. Let's spice it up by adding a second layer. You noticed that I called the first 'far' right, that's because I had in mind to a 'near' layer since the beginning. | ||
+ | |||
+ | This second layer will be built just as the first. Grab the texture file from '''[[Media:2D Spaceshooter Star near.png|here]]''', rename it to ''''star_near.png'''', and add it to your project under the 'gfx' folder. | ||
− | + | Now change the 'Background:init' function to load the new texture and add a second Pixel sprite on top of our background object: | |
− | < | + | <syntaxhighlight lang="lua"> |
function Background:init(w,h) | function Background:init(w,h) | ||
− | local far=Texture.new("gfx/star_far.jpg",true, { wrap=TextureBase.REPEAT }) | + | -- load the textures |
− | -- | + | local far = Texture.new("gfx/star_far.jpg", true, { wrap=TextureBase.REPEAT }) |
+ | local near = Texture.new("gfx/star_near.png", true, { wrap=TextureBase.REPEAT }) | ||
+ | -- set up the far view | ||
self:setTexture(far) | self:setTexture(far) | ||
− | self:setDimensions(w,h) | + | self:setDimensions(w, h) |
+ | -- create the near view on top of the far view | ||
+ | self.near = Pixel.new(near, w, h) | ||
+ | self:addChild(self.near) | ||
+ | -- start at position 0. We don't really care what that means since our texture is repeating | ||
+ | self.position = 0 | ||
end | end | ||
− | </ | + | </syntaxhighlight> |
− | + | ||
− | + | Noticed the last line? We are going to move our background with the 'Background:advance' method, so that last line initializes the position to 0. You can check the result of the addition of the second layer by running your project. | |
− | + | ||
+ | Let's now add a bit of movement in our 'Background:advance' method: | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | function Background:advance(amount) | ||
+ | self.position += amount | ||
+ | self:setTexturePosition(0, self.position/3) -- the far view advances much slower | ||
+ | self.near:setTexturePosition(0, self.position) | ||
+ | end | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | This piece of code is rather straightforward: we incremement our position with the given amount, then change our two textures ''y'' origins, making them scroll vertically. The trick here is to move the 'far' layer slower than the 'near' layer, to give an impression of depth. | ||
+ | |||
+ | If all goes well, you should end up with the following result: | ||
+ | |||
+ | {{#widget:GApp|app=SpaceShooter_BG2.GApp|width=320|height=480}} | ||
+ | |||
+ | Nice isn't it ? | ||
− | |||
− | |||
− | |||
− | + | Prev: [[2D Space Shooter Part 1: Planning, Tooling, Layers, Objects]]</br> | |
+ | '''Next: [[2D Space Shooter Part 3: Ships]]''' | ||
− | |||
− | + | '''[[Tutorial - Making a 2D space shooter game]]''' | |
− | + | {{GIDEROS IMPORTANT LINKS}} |
Latest revision as of 12:28, 15 November 2023
A bit of set up
Let's begin by setting up some folder.
We want a scrolling starry background, so let's add it to the project:
- Create a folder named 'gfx' under 'Files' in your gideros project
- Download the starry background texture (not the thumbnail on the right) on your computer, rename it to 'star_far.jpg', and add it to your project under the 'gfx' folder you just created
Because it is repeatable, its dimensions must be a power of two. I choosed a width of 512 for the texture, so let's configure our project according to that width.
In your project settings (create a new project for this tutorial if not already done!), go to the 'Graphics' tab and set:
- Scale mode to 'Fit Width'
- Logical dimensions to 512 x 1024
- Orientation to portrait
This will ensure that whatever the phone size is, our canvas will have a width of 512. The height doesn't matter actually, you can use anything instead of 1024 but it is convenient to specify something sensible, that is taller than the width, since we want our game to be in portrait.
However we'll need to figure out the actual height and logical position of our screen at run time, in order to place our graphics on the stage.
The code below will do what we want. Create a main.lua file and add the following code:
-- compute screen bounds
SCR_LEFT, SCR_TOP, SCR_RIGHT, SCR_BOTTOM = application:getLogicalBounds()
Here we ask Gideros for the actual screen bounds in canvas space, and store them in global variables so that they can be accessed from anywhere in the code. They will be useful for positionning objects, but also for checking if objects go out of bounds.
To avoid confusion, I'll always declare my global variables in upper case, but of course you are free to use another convention.
Next step is to create our background and put it on stage. To keep our code clean, let's handle the background image as an object of class Background. We'll define it later.
-- create the background object with the screen size
-- width should be 512 per project settings, but we'll compute it anyway to avoid relying on constants
local background = Background.new(SCR_RIGHT-SCR_LEFT, SCR_BOTTOM-SCR_TOP)
-- add it to stage
stage:addChild(background)
background:setY(SCR_TOP)
Our background will move as our spaceship progress through the game. We'll make it 'advance' in our game loop:
-- this is our game loop
stage:addEventListener(Event.ENTER_FRAME, function()
background:advance(1)
end)
The code of our game loop will be called on each animation frame, 60 times per second. In it, we call the 'advance' method of the background object with a parameter telling it by how much we want to move. This will allow us to alter game speed later if we ever need to.
So far our main.lua contains:
-- compute screen bounds
SCR_LEFT, SCR_TOP, SCR_RIGHT, SCR_BOTTOM = application:getLogicalBounds()
-- create the background object with the screen size
-- width should be 512 per project settings, but we'll compute it anyway to avoid relying on constants
local background = Background.new(SCR_RIGHT-SCR_LEFT, SCR_BOTTOM-SCR_TOP)
-- add it to stage
stage:addChild(background)
background:setY(SCR_TOP)
-- this is our game loop
stage:addEventListener(Event.ENTER_FRAME, function()
background:advance(1)
end)
You can try to launch your project, but it won't run yet because we didn't define the 'Background' class.
A basic background object
Create a new lua file and call it background.lua. In this file, we'll define the 'Background' class.
Our background object will mostly display an image, so let our Background class be a subclass of Gideros Pixel object:
-- our background will be a subclass of Gideros Pixel
-- here we pass an empty constructor function so that the Pixel is initialized with defaults
Background = Core.class(Pixel, function() end)
The second argument to the Core.class function is used to specify the parameters to use when constructing the 'Pixel' we inherit from. Here this is just an empty function, meaning that we don't pass any parameters to the Pixel.new that will be called internally.
Now let's add empty functions for our class so that our code runs.
-- initialize our background with the given width and height
function Background:init(w, h)
end
function Background:advance(amount)
end
The 'init' method will be called by main.lua during 'Background.new', while the 'advance' method is called from our main loop. If you start a Gideros Player and hit play, our code will run, but won't show anything.
Let's add some code to load our texture and attach it to our background:
function Background:init(w, h)
local far = Texture.new("gfx/star_far.jpg", true, { wrap=TextureBase.REPEAT })
-- set up the far view
self:setTexture(far)
self:setDimensions(w, h)
end
This tells Gideros to load our image called 'star_far.jpg' from the 'gfx' folder of our project structure, and make it a texture. We also ask Gideros to enable filtering ('true') and that the texture should repeat the image ('{ wrap=TextureBase.REPEAT }'). We then assign this texture to our background object. Remember that Background is a subclass of Pixel, so it has all the properties and methods of a Pixel object.
The final line changes the size of the background to the dimensions given in arguments.
Our project should now run and show a space background.
A nicer background
Something is shown, but it doesn't look quite good for a space shooter game. Let's spice it up by adding a second layer. You noticed that I called the first 'far' right, that's because I had in mind to a 'near' layer since the beginning.
This second layer will be built just as the first. Grab the texture file from here, rename it to 'star_near.png', and add it to your project under the 'gfx' folder.
Now change the 'Background:init' function to load the new texture and add a second Pixel sprite on top of our background object:
function Background:init(w,h)
-- load the textures
local far = Texture.new("gfx/star_far.jpg", true, { wrap=TextureBase.REPEAT })
local near = Texture.new("gfx/star_near.png", true, { wrap=TextureBase.REPEAT })
-- set up the far view
self:setTexture(far)
self:setDimensions(w, h)
-- create the near view on top of the far view
self.near = Pixel.new(near, w, h)
self:addChild(self.near)
-- start at position 0. We don't really care what that means since our texture is repeating
self.position = 0
end
Noticed the last line? We are going to move our background with the 'Background:advance' method, so that last line initializes the position to 0. You can check the result of the addition of the second layer by running your project.
Let's now add a bit of movement in our 'Background:advance' method:
function Background:advance(amount)
self.position += amount
self:setTexturePosition(0, self.position/3) -- the far view advances much slower
self.near:setTexturePosition(0, self.position)
end
This piece of code is rather straightforward: we incremement our position with the given amount, then change our two textures y origins, making them scroll vertically. The trick here is to move the 'far' layer slower than the 'near' layer, to give an impression of depth.
If all goes well, you should end up with the following result:
Nice isn't it ?
Prev: 2D Space Shooter Part 1: Planning, Tooling, Layers, Objects
Next: 2D Space Shooter Part 3: Ships
Tutorial - Making a 2D space shooter game