You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Mar 8, 2021. It is now read-only.
This post will cover how to create an automatic game updater for your LÖVE game. The main benefit of having an automatic game updater is only having to send players an executable once and then from there they'll always be up to date without having to manually download anything else. I was surprised at how simple this was to get working with LÖVE so I thought I'd share.
Intro
Before we get into the meat of this there's some base knowledge required:
LÖVE games can be distributed in two ways, either as a .love file or as an executable. An executable is an executable and a .love file is simply a renamed zip with its contents arranged in a certain way: main.lua, which is the entry point of LÖVE programs, has to be a top level file on the zip (see more about this here).
There's a function called love.filesystem.mount which lets you mount a zip file or folder from the game's save directory into the current environment. So say you have a bunch of zipped assets in the save directory and you want to access them. You'd do something like this:
.love files are renamed zips and love.filesystem.mount mounts zips, so it stands to reason that .love files can be mounted into the current environment.
When you use require to load a file in a Lua program, it's cached in the package.loaded table. To reload a file while the game is running we can just nil its reference in package.loaded and then require it again. This is the main way in which you can achieve live coding features with Lua/LÖVE (see lurker/lume). For instance, say the programmer changed the Player.lua file and you want to reload it while the game is running so that you don't have to close + rerun the whole program again. What you'd do is create a reload function and then call this function whenever needed:
functionreload()
package.loaded.Player=nilPlayer=require('Player')
end
Updater
With all that super hot info in mind we can start connecting the dots and getting ideas about maybe how the auto-updater might work. But I'll give you a hint, it has to do with:
Reloads everything by calling love.init() and love.load()
And there you have it! The game was mounted into the updater's environment, the current main.lua (and conf.lua, which is LÖVE's configuration file) was unloaded and then everything was reloaded again with love.init() and love.load(), but since the updater's code was just unloaded, all that's left is the mounted code that was inside the .love file, which means that instead of reloading the updater's code we reload the game's code (since main.lua is a top level file).
So now whenever the user runs the updater it will first do all checks it needs to do to see if there's a new version available, download it if there is and then it will load the game, which is just a .love file in the user's save directory.
Code
We'll use two libraries to do this: async so we can make an HTTP request without locking the LÖVE thread and luajit-request so we can make an HTTP request.
One thing I failed to mention about how the updater works is the version checking logic. The way I'm doing it is that I have a version file that is automatically updated and uploaded somewhere as I commit and push the game to version control. This file will then be used so that the updater can check which is the most up to date version, so that if the current version on the user's computer is lower, the new one will be downloaded.
Version checks
Anyway, after getting those libraries you can create the first HTTP request for the version file:
localasync=require('async')
functionlove.load(args)
-- Define asynchronous version requestlocalversion_request=async.define('version_request', function()
localrequest=require('luajit-request')
localresponse=request.send(linktotheversionfile)
returnresponse.body, response.codeend)
-- Request the versionlocalversion=nilversion_request(function(result, status)
ifstatus==200thenversion=getVersion(result) -- define getVersion however you want based on your version file endend)
endfunctionlove.update(dt)
async.update()
end
So now if everything went right we should have to most up to date version number in the version variable. Now what we need to do is check to see if the version that exists on the user's save directory matches the one in the version variable or not. The way I do this is based on the .love file's name. After downloading the game, I always save it like this:
This writes the result of the HTTP request that we haven't written yet (the one that downloads the game) as the file game_1.0.2.love, for instance. So assuming that this is the case, for us to check versions all we have to do is:
-- Request the versionversion_request(function(result, status)
ifstatus==200thenversion=getVersion(result)
ifnotlove.filesystem.isFile('game_' ..version..'.love') then-- download the new version, mount and run the gameelse-- mount the existing version and run the gameendendend)
Downloading the game
Now for the game HTTP request. This is very similar to the previous one, with the only difference that we have to pass in the version variable (at least the way I'm doing it, but you could be doing version checks in a way that doesn't require this, either way, you get the general idea):
-- Define asynchronous game requestlocalgame_request=async.define('game_request', function(version)
localrequest=require('luajit-request')
localresponse=request.send(linktotheversionfileusingtheversionvariable)
returnresponse.body, response.codeend)
And
-- Request the versionversion_request(function(result, status)
ifstatus==200thenversion=getVersion(result)
ifnotlove.filesystem.isFile('game_' ..version..'.love') then-- Request the gamegame_request(function(result, status)
ifstatus==200thenlove.filesystem.write('game_' ..version..'.love', result)
-- mount and runend, version)
else-- mount and runendendend)
Now all there's left is the part where we mount and run the game.
Mount and run
This one is rather straight forward. All we have to do is, as previously stated, mount the .love file, unload main.lua and conf.lua, then call love.init() and love.load():
-- Request the versionversion_request(function(result, status)
ifstatus==200thenversion=getVersion(result)
ifnotlove.filesystem.isFile('game_' ..version..'.love') then-- Request the gamegame_request(function(result, status)
ifstatus==200thenlove.filesystem.write('game_' ..version..'.love', result)
-- Mount and runlove.filesystem.mount('game_' ..version..'.love', '')
package.loaded.main=nilpackage.loaded.conf=nillove.conf=nillove.init()
love.load(args)
end, version)
else-- Mount and runlove.filesystem.mount('game_' ..version..'.love', '')
package.loaded.main=nilpackage.loaded.conf=nillove.conf=nillove.init()
love.load(args)
endendend)
END
And after this everything should work fine. Of course there's a lot of stuff you can and should add, like error checking. Or some super cool animation with your game's title being engulfed in the fiery flames of hell. Or a mini-game if the download is big. Whatever, you can do anything because the updater is a LÖVE game as well so you can code whatever you want in it, and since the HTTP request is asynchronous it will run in the background while the main LÖVE thread does it's own stuff. You can find the full file here and credit goes to Billiam for creating a gist that does the same thing which was what I used to guide me through the shadowy paths of confusion
The text was updated successfully, but these errors were encountered:
Because you have to figure out which files you have to update and which ones you don't- or just update all the files, but that would be pretty slow for big games.
Edit: I mean mod-able games
prust
added a commit
to prust/game-designer
that referenced
this issue
Apr 15, 2017
2015-08-21 17:30
This post will cover how to create an automatic game updater for your LÖVE game. The main benefit of having an automatic game updater is only having to send players an executable once and then from there they'll always be up to date without having to manually download anything else. I was surprised at how simple this was to get working with LÖVE so I thought I'd share.
Intro
Before we get into the meat of this there's some base knowledge required:
LÖVE games can be distributed in two ways, either as a
.love
file or as an executable. An executable is an executable and a.love
file is simply a renamed zip with its contents arranged in a certain way:main.lua
, which is the entry point of LÖVE programs, has to be a top level file on the zip (see more about this here).There's a function called love.filesystem.mount which lets you mount a zip file or folder from the game's save directory into the current environment. So say you have a bunch of zipped assets in the save directory and you want to access them. You'd do something like this:
.love
files are renamed zips andlove.filesystem.mount
mounts zips, so it stands to reason that.love
files can be mounted into the current environment.When you use
require
to load a file in a Lua program, it's cached in thepackage.loaded
table. To reload a file while the game is running we can justnil
its reference inpackage.loaded
and thenrequire
it again. This is the main way in which you can achieve live coding features with Lua/LÖVE (see lurker/lume). For instance, say the programmer changed thePlayer.lua
file and you want to reload it while the game is running so that you don't have to close + rerun the whole program again. What you'd do is create areload
function and then call this function whenever needed:Updater
With all that super hot info in mind we can start connecting the dots and getting ideas about maybe how the auto-updater might work. But I'll give you a hint, it has to do with:
.love
file and upload it somewhere;.love
file.love
filepackage.loaded.main = nil
,package.loaded.conf = nil
love.init()
andlove.load()
And there you have it! The game was mounted into the updater's environment, the current
main.lua
(andconf.lua
, which is LÖVE's configuration file) was unloaded and then everything was reloaded again withlove.init()
andlove.load()
, but since the updater's code was just unloaded, all that's left is the mounted code that was inside the.love
file, which means that instead of reloading the updater's code we reload the game's code (sincemain.lua
is a top level file).So now whenever the user runs the updater it will first do all checks it needs to do to see if there's a new version available, download it if there is and then it will load the game, which is just a
.love
file in the user's save directory.Code
We'll use two libraries to do this: async so we can make an HTTP request without locking the LÖVE thread and luajit-request so we can make an HTTP request.
One thing I failed to mention about how the updater works is the version checking logic. The way I'm doing it is that I have a version file that is automatically updated and uploaded somewhere as I commit and push the game to version control. This file will then be used so that the updater can check which is the most up to date version, so that if the current version on the user's computer is lower, the new one will be downloaded.
Version checks
Anyway, after getting those libraries you can create the first HTTP request for the version file:
So now if everything went right we should have to most up to date version number in the
version
variable. Now what we need to do is check to see if the version that exists on the user's save directory matches the one in theversion
variable or not. The way I do this is based on the.love
file's name. After downloading the game, I always save it like this:This writes the result of the HTTP request that we haven't written yet (the one that downloads the game) as the file
game_1.0.2.love
, for instance. So assuming that this is the case, for us to check versions all we have to do is:Downloading the game
Now for the game HTTP request. This is very similar to the previous one, with the only difference that we have to pass in the
version
variable (at least the way I'm doing it, but you could be doing version checks in a way that doesn't require this, either way, you get the general idea):And
Now all there's left is the part where we mount and run the game.
Mount and run
This one is rather straight forward. All we have to do is, as previously stated, mount the
.love
file, unloadmain.lua
andconf.lua
, then calllove.init()
andlove.load()
:END
And after this everything should work fine. Of course there's a lot of stuff you can and should add, like error checking. Or some super cool animation with your game's title being engulfed in the fiery flames of hell. Or a mini-game if the download is big. Whatever, you can do anything because the updater is a LÖVE game as well so you can code whatever you want in it, and since the HTTP request is asynchronous it will run in the background while the main LÖVE thread does it's own stuff. You can find the full file here and credit goes to Billiam for creating a gist that does the same thing which was what I used to guide me through the shadowy paths of confusion
The text was updated successfully, but these errors were encountered: