A module/mixin system written in the Lua programming language.
A module can be thought of as a unit (of code), which is used to facilitate a more complex purpose (our program). Lua doesn't naturally come pre-built with the idea of a class, however it offers the power of metatables to imitate inheritance. This idea is the main idea behind Modern, but with a bit more.
Inheritance - any modules can extend any other module.
Mixins - extend your modules beyond their ability without affecting the inheritance chain. Functions with the same name will compound into one function call.
Utility Functions - check out the API
Direct Download
- Download the latest release from Modern's release page.
- Unpack and upload to a folder that is recognized by
LUA_PATH.
LuaRocks
luarocks install modern
- Simply include
modernwithin a new file.
local Modern = require 'modern'- Extend from
Modernto create a fresh module, inheriting all it's functionality.
local Player = Modern:extend()- Now you can add additional functionality to your module.
-- `new` automatically runs when Module is called
function Player:new(x, y)
self.x = x
self.y = y
endModern allows you to create polymorphic relationships with other Modules.
local Modern = require 'modern'
local Enemy = Modern:extend() -- inherits everything from `Modern`
local Orc = Enemy:extend() -- inherits everything from `Enemy`
local Troll = Enemy:extend() -- inherits everything from `Enemy`Mixins are added as arguments when calling extend. You can add another Module or a basic table as an argument. Any functions with conflicting names will compound so that they are all fired in sequence when called.
local Modern = require 'modern'
local AABB = require 'mixins.AABB'
local FSM = require 'mixins.FSM'
local Enemy = Modern:extend(AABB, FSM)A use case for using Mixins would be adding a Finite State Machine to your Module (in this case Enemy). It doesn't make sense to inherit from FSM, but we want to include the functionality to update our Enemy states each game cycle. By adding FSM as a mixin expands the base Module's functionality.
Every Module is provided a name & namespace upon creation (__call() and extend()). The __name is just the variable name you assigned the Module, while the __namespace can be thought of as it's inheritance path. Here's an example:
local Modern = require 'modern'
local Enemy = Modern:extend()
local Troll = Enemy:extend()
print(Troll.__name) -- prints "Troll"
print(Troll.__namespace) -- prints "Modern\Enemy\Troll"In this example we create a simple enemy hierarchy. Notice the call to the parent's new function: self.__super.new(self, x, y). If not called, the parent's new would be skipped. Our Gnome module sets it's own attack power, which will override the attack value from 5 to 10.
local Modern = require 'modern'
--
local Enemy = Modern:extend()
function Enemy:new(x, y)
self.x = x
self.y = y
print('Enemy:new', x, y)
end
--
local Gnome = Enemy:extend()
function Gnome:new(x, y, attack)
self.__super.new(self, x, y) -- call parent `new`
self.attack = attack
print(self.__name .. ':new', x, y, attack)
end
function Gnome:strike()
print(self.__name .. ' strikes for ' .. self.attack)
endRunning the code...
$ lua
> gnome = Gnome(70, 80, 10) # Enemy:new 70, 80
# Gnome:new 70, 80, 10
> print(gnome.x, gnome.y) # 70, 80
> print(gnome.attack) # 10
> gnome:strike() # Gnome strikes for 10In this (silly) example we'll show an example using mixins and how conflicting function names are handled.
local Modern = require 'modern'
--
local M1 = Modern:extend()
function M1:new() print('M1:new') end
function M1:foo() print('M1:foo') end
--
local M2 = Modern:extend()
function M2:new() print('M2:new') end
function M2:foo() print('M2:foo') end
--
local MM = Modern:extend(M1, M2)
function MM:new() print('MM:new') end
function MM:foo() print('MM:foo') endRunning the code...
$ lua
> mm = MM() # MM:new
# M1:new
# M2:new
> mm:foo() # MM:foo
# M1:foo
# M2:fooNotice how all 3 foo functions are called (in order of inclusion).
Important: All
Mixinsand their functions must be declared before adding them via theextendfunction. This is due to how Lua's__indexand__newindexmetamethods work (see here).
Love2D is a fantastic framework to get you up and running with graphics, audio, and easy window configurations. This example shows how to use Modern to draw multiple layers using Mixins.
First we'll create a Player module including an AABB module, which provides axis-aligned bounding box functionality for collision, and in our example, debugging.
-- player.lua
local Modern = require 'modern'
--
local AABB = Modern:extend()
function AABB:new()
-- using `Player` variables to create some more
self.left = self.x
self.top = self.y
self.right = self.x + self.width
self.bottom = self.y + self.height
end
-- ...
-- Really cool, useful functions removed for brevity :p
-- ...
function AABB:draw()
if self.debug then
love.graphics.setColor(1, 0, 0, 1)
love.graphics.draw(self.image, self.x, self.y, self.width, self.height)
end
end
--
local Player = Modern:extend(AABB)
function Player:new(x, y, src)
local image = love.graphics.newImage(src)
local w, h = image:getDimensions( )
self.x = x
self.y = y
self.image = image
self.scale = 0.5
self.width = w * self.scale
self.height = h * self.scale
self.debug = false
end
function Player:draw()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(self.image, self:center())
end
return PlayerNext, using Love2D we draw the player instance.
-- main.lua
local Player = require 'player'
local player
function love.load()
player = Player(50, 50, 'player.png')
player.debug = true
end
function love.draw()
player:draw()
endFinally, our reward!
__call - Create new Module instance.
is(obj) - Checks if Module is a (or inherits from)...
has(obj) - Checks Module for inclusion of a Mixin.
super(obj) - Fetch super Module of current Module.
copy() - shallow copy (using rawset) of Module.
clone() - Deep copy (including metatables) of Module.
extend(...) - Extend from another Module inheriting all it's goodies.
__tostring() - Visualization of Module showing properties.
$ lua
> print(MyExampleModule)
# ---------------------------------------------------------------------------
# | [ ] | Module | Namespace | DataType | Key | Value |
---------------------------------------------------------------------------
# | [-] | Orc | Modern\Enemy\Orc | number | attack | 100 |
# | [-] | Orc | Modern\Enemy\Orc | function | new | <function> |
# | [-] | Orc | Modern\Enemy\Orc | number | y | 2 |
# | [-] | Orc | Modern\Enemy\Orc | number | x | 1 |
# | [-] | Orc | Modern\Enemy\Orc | number | health | 100 |
# | [^] | Enemy | Modern\Enemy | function | new | <function> |
# | [-] | Modern | Modern | function | extend | <function> |
# | [-] | Modern | Modern | function | copy | <function> |
# | [-] | Modern | Modern | function | super | <function> |
# | [-] | Modern | Modern | function | clone | <function> |
# | [-] | Modern | Modern | function | has | <function> |
# | [-] | Modern | Modern | function | is | <function> |
# | [+] | Health | Modern\Health | function | new | <function> |
# | [+] | Health | Modern\Health | function | heal | <function> |
# | [+] | Health | Modern\Health | function | hit | <function> |
# | [+] | Combat | Modern\Combat | function | defend | <function> |
# | [+] | Combat | Modern\Combat | function | new | <function> |
# | [+] | Combat | Modern\Combat | function | hit | <function> |
# ---------------------------------------------------------------------------Note:
[-]normal property,[^]- overridden property,[+]- mixin function
This project is licensed under the MIT License - see the LICENSE.md file for details
