Use middleclass for OOP

This commit is contained in:
Jaidyn Ann 2020-09-19 01:53:57 -05:00
parent 8fa50c2916
commit 04034ef5e2
38 changed files with 1807 additions and 1006 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 898 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

View File

@ -1,368 +0,0 @@
local baton = {
_VERSION = 'Baton v1.0.1',
_DESCRIPTION = 'Input library for LÖVE.',
_URL = 'https://github.com/tesselode/baton',
_LICENSE = [[
MIT License
Copyright (c) 2019 Andrew Minnich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
}
-- string parsing functions --
-- splits a source definition into type and value
-- example: 'button:a' -> 'button', 'a'
local function parseSource(source)
return source:match '(.+):(.+)'
end
-- splits an axis value into axis and direction
-- example: 'leftx-' -> 'leftx', '-'
local function parseAxis(value)
return value:match '(.+)([%+%-])'
end
-- splits a joystick hat value into hat number and direction
-- example: '2rd' -> '2', 'rd'
local function parseHat(value)
return value:match '(%d)(.+)'
end
--[[
-- source functions --
each source function checks the state of one type of input
and returns a value from 0 to 1. for binary controls, such
as keyboard keys and gamepad buttons, they return 1 if the
input is held down and 0 if not. for analog controls, such
as "leftx+" (the left analog stick held to the right), they
return a number from 0 to 1.
source functions are split into keyboard/mouse functions
and joystick/gamepad functions. baton treats these two
categories slightly differently.
]]
local sourceFunction = {keyboardMouse = {}, joystick = {}}
-- checks whether a keyboard key is down or not
function sourceFunction.keyboardMouse.key(key)
return love.keyboard.isDown(key) and 1 or 0
end
-- checks whether a keyboard key is down or not,
-- but it takes a scancode as an input
function sourceFunction.keyboardMouse.sc(sc)
return love.keyboard.isScancodeDown(sc) and 1 or 0
end
-- checks whether a mouse buttons is down or not.
-- note that baton doesn't detect mouse movement, just the buttons
function sourceFunction.keyboardMouse.mouse(button)
return love.mouse.isDown(tonumber(button)) and 1 or 0
end
-- checks the position of a joystick axis
function sourceFunction.joystick.axis(joystick, value)
local axis, direction = parseAxis(value)
-- "a and b or c" is ok here because b will never be boolean
value = tonumber(axis) and joystick:getAxis(tonumber(axis))
or joystick:getGamepadAxis(axis)
if direction == '-' then value = -value end
return value > 0 and value or 0
end
-- checks whether a joystick button is held down or not
-- can take a number or a GamepadButton string
function sourceFunction.joystick.button(joystick, button)
-- i'm intentionally not using the "a and b or c" idiom here
-- because joystick.isDown returns a boolean
if tonumber(button) then
return joystick:isDown(tonumber(button)) and 1 or 0
else
return joystick:isGamepadDown(button) and 1 or 0
end
end
-- checks the direction of a joystick hat
function sourceFunction.joystick.hat(joystick, value)
local hat, direction = parseHat(value)
return joystick:getHat(hat) == direction and 1 or 0
end
--[[
-- player class --
the player object takes a configuration table and handles input
accordingly. it's called a "player" because it makes sense to use
multiple of these for each player in a multiplayer game, but
you can use separate player objects to organize inputs
however you want.
]]
local Player = {}
Player.__index = Player
-- internal functions --
-- sets the player's config to a user-defined config table
-- and sets some defaults if they're not already defined
function Player:_loadConfig(config)
if not config then
error('No config table provided', 4)
end
if not config.controls then
error('No controls specified', 4)
end
config.pairs = config.pairs or {}
config.deadzone = config.deadzone or .5
config.squareDeadzone = config.squareDeadzone or false
self.config = config
end
-- initializes a control object for each control defined in the config
function Player:_initControls()
self._controls = {}
for controlName, sources in pairs(self.config.controls) do
self._controls[controlName] = {
sources = sources,
rawValue = 0,
value = 0,
down = false,
downPrevious = false,
pressed = false,
released = false,
}
end
end
-- initializes an axis pair object for each axis pair defined in the config
function Player:_initPairs()
self._pairs = {}
for pairName, controls in pairs(self.config.pairs) do
self._pairs[pairName] = {
controls = controls,
rawX = 0,
rawY = 0,
x = 0,
y = 0,
down = false,
downPrevious = false,
pressed = false,
released = false,
}
end
end
function Player:_init(config)
self:_loadConfig(config)
self:_initControls()
self:_initPairs()
self._activeDevice = 'none'
end
--[[
detects the active device (keyboard/mouse or joystick).
if the keyboard or mouse is currently being used, joystick
inputs will be ignored. this is to prevent slight axis movements
from adding errant inputs when someone's using the keyboard.
the active device is saved to player._activeDevice, which is then
used throughout the rest of the update loop to check only
keyboard or joystick inputs.
]]
function Player:_setActiveDevice()
for _, control in pairs(self._controls) do
for _, source in ipairs(control.sources) do
local type, value = parseSource(source)
if sourceFunction.keyboardMouse[type] then
if sourceFunction.keyboardMouse[type](value) > self.config.deadzone then
self._activeDevice = 'kbm'
return
end
elseif self.config.joystick and sourceFunction.joystick[type] then
if sourceFunction.joystick[type](self.config.joystick, value) > self.config.deadzone then
self._activeDevice = 'joy'
end
end
end
end
end
--[[
gets the value of a control by running the appropriate source functions
for all of its sources. does not apply deadzone.
]]
function Player:_getControlRawValue(control)
local rawValue = 0
for _, source in ipairs(control.sources) do
local type, value = parseSource(source)
if sourceFunction.keyboardMouse[type] and self._activeDevice == 'kbm' then
if sourceFunction.keyboardMouse[type](value) == 1 then
return 1
end
elseif sourceFunction.joystick[type] and self._activeDevice == 'joy' then
rawValue = rawValue + sourceFunction.joystick[type](self.config.joystick, value)
if rawValue >= 1 then
return 1
end
end
end
return rawValue
end
--[[
updates each control in a player. saves the value with and without deadzone
and the down/pressed/released state.
]]
function Player:_updateControls()
for _, control in pairs(self._controls) do
control.rawValue = self:_getControlRawValue(control)
control.value = control.rawValue >= self.config.deadzone and control.rawValue or 0
control.downPrevious = control.down
control.down = control.value > 0
control.pressed = control.down and not control.downPrevious
control.released = control.downPrevious and not control.down
end
end
--[[
updates each axis pair in a player. saves the value with and without deadzone
and the down/pressed/released state.
]]
function Player:_updatePairs()
for _, pair in pairs(self._pairs) do
-- get raw x and y
local l = self._controls[pair.controls[1]].rawValue
local r = self._controls[pair.controls[2]].rawValue
local u = self._controls[pair.controls[3]].rawValue
local d = self._controls[pair.controls[4]].rawValue
pair.rawX, pair.rawY = r - l, d - u
-- limit to 1
local len = math.sqrt(pair.rawX^2 + pair.rawY^2)
if len > 1 then
pair.rawX, pair.rawY = pair.rawX / len, pair.rawY / len
end
-- deadzone
if self.config.squareDeadzone then
pair.x = math.abs(pair.rawX) > self.config.deadzone and pair.rawX or 0
pair.y = math.abs(pair.rawY) > self.config.deadzone and pair.rawY or 0
else
pair.x = len > self.config.deadzone and pair.rawX or 0
pair.y = len > self.config.deadzone and pair.rawY or 0
end
-- down/pressed/released
pair.downPrevious = pair.down
pair.down = pair.x ~= 0 or pair.y ~= 0
pair.pressed = pair.down and not pair.downPrevious
pair.released = pair.downPrevious and not pair.down
end
end
-- public API --
-- checks for changes in inputs
function Player:update()
self:_setActiveDevice()
self:_updateControls()
self:_updatePairs()
end
-- gets the value of a control or axis pair without deadzone applied
function Player:getRaw(name)
if self._pairs[name] then
return self._pairs[name].rawX, self._pairs[name].rawY
elseif self._controls[name] then
return self._controls[name].rawValue
else
error('No control with name "' .. name .. '" defined', 3)
end
end
-- gets the value of a control or axis pair with deadzone applied
function Player:get(name)
if self._pairs[name] then
return self._pairs[name].x, self._pairs[name].y
elseif self._controls[name] then
return self._controls[name].value
else
error('No control with name "' .. name .. '" defined', 3)
end
end
-- gets whether a control or axis pair is "held down"
function Player:down(name)
if self._pairs[name] then
return self._pairs[name].down
elseif self._controls[name] then
return self._controls[name].down
else
error('No control with name "' .. name .. '" defined', 3)
end
end
-- gets whether a control or axis pair was pressed this frame
function Player:pressed(name)
if self._pairs[name] then
return self._pairs[name].pressed
elseif self._controls[name] then
return self._controls[name].pressed
else
error('No control with name "' .. name .. '" defined', 3)
end
end
-- gets whether a control or axis pair was released this frame
function Player:released(name)
if self._pairs[name] then
return self._pairs[name].released
elseif self._controls[name] then
return self._controls[name].released
else
error('No control with name "' .. name .. '" defined', 3)
end
end
--[[
gets the currently active device (either "kbm", "joy", or "none").
this is useful for displaying instructional text. you may have
a menu that says "press ENTER to confirm" or "press A to confirm"
depending on whether the player is using their keyboard or gamepad.
this function allows you to detect which they used most recently.
]]
function Player:getActiveDevice()
return self._activeDevice
end
-- main functions --
-- creates a new player with the user-provided config table
function baton.new(config)
local player = setmetatable({}, Player)
player:_init(config)
return player
end
return baton

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Andrew Minnich
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,100 +0,0 @@
local baton = require 'baton'
local player = baton.new {
controls = {
left = {'key:left', 'axis:leftx-', 'button:dpleft'},
right = {'key:right', 'axis:leftx+', 'button:dpright'},
up = {'key:up', 'axis:lefty-', 'button:dpup'},
down = {'key:down', 'axis:lefty+', 'button:dpdown'},
action = {'key:x', 'button:a', 'mouse:1'},
},
pairs = {
move = {'left', 'right', 'up', 'down'}
},
joystick = love.joystick.getJoysticks()[1],
deadzone = .33,
}
local pairDisplayAlpha = 0
local pairDisplayTargetAlpha = 0
local buttonDisplayAlpha = 0
local buttonDisplayTargetAlpha = 0
local updates = 0
local updateTime = 0
function love.update(dt)
local time = love.timer.getTime()
player:update()
pairDisplayTargetAlpha = player:pressed 'move' and 1
or player:released 'move' and 1
or player:down 'move' and .5
or 0
if pairDisplayAlpha > pairDisplayTargetAlpha then
pairDisplayAlpha = pairDisplayAlpha - 4 * dt
end
if pairDisplayAlpha < pairDisplayTargetAlpha then
pairDisplayAlpha = pairDisplayTargetAlpha
end
buttonDisplayTargetAlpha = player:pressed 'action' and 1
or player:released 'action' and 1
or player:down 'action' and .5
or 0
if buttonDisplayAlpha > buttonDisplayTargetAlpha then
buttonDisplayAlpha = buttonDisplayAlpha - 4 * dt
end
if buttonDisplayAlpha < buttonDisplayTargetAlpha then
buttonDisplayAlpha = buttonDisplayTargetAlpha
end
updateTime = updateTime + (love.timer.getTime() - time)
updates = updates + 1
end
function love.keypressed(key)
if key == 'escape' then
love.event.quit()
end
end
local pairDisplayRadius = 128
function love.draw()
love.graphics.setColor(1, 1, 1)
love.graphics.print('Current active device: ' .. tostring(player:getActiveDevice()))
love.graphics.print('Average update time (us): ' .. math.floor(updateTime/updates*1000000), 0, 16)
love.graphics.print('Memory usage (kb): ' .. math.floor(collectgarbage 'count'), 0, 32)
love.graphics.push()
love.graphics.translate(400, 300)
love.graphics.setColor(.25, .25, .25, pairDisplayAlpha)
love.graphics.circle('fill', 0, 0, pairDisplayRadius)
love.graphics.setColor(1, 1, 1)
love.graphics.circle('line', 0, 0, pairDisplayRadius)
local r = pairDisplayRadius * player.config.deadzone
if player.config.squareDeadzone then
love.graphics.rectangle('line', -r, -r, r*2, r*2)
else
love.graphics.circle('line', 0, 0, r)
end
love.graphics.setColor(.5, .5, .5)
local x, y = player:getRaw 'move'
love.graphics.circle('fill', x*pairDisplayRadius, y*pairDisplayRadius, 4)
love.graphics.setColor(1, 1, 1)
x, y = player:get 'move'
love.graphics.circle('fill', x*pairDisplayRadius, y*pairDisplayRadius, 4)
love.graphics.setColor(1, 1, 1)
love.graphics.rectangle('line', -50, 150, 100, 100)
love.graphics.setColor(1, 1, 1, buttonDisplayAlpha)
love.graphics.rectangle('fill', -50, 150, 100, 100)
love.graphics.pop()
end

View File

@ -1,142 +0,0 @@
# Baton
**Baton** is an input library for LÖVE that bridges the gap between keyboard and joystick controls and allows you to easily define and change controls on the fly. You can create multiple independent input managers, which you can use for multiplayer games or other organizational purposes.
```lua
local baton = require 'baton'
local input = baton.new {
controls = {
left = {'key:left', 'key:a', 'axis:leftx-', 'button:dpleft'},
right = {'key:right', 'key:d', 'axis:leftx+', 'button:dpright'},
up = {'key:up', 'key:w', 'axis:lefty-', 'button:dpup'},
down = {'key:down', 'key:s', 'axis:lefty+', 'button:dpdown'},
action = {'key:x', 'button:a'},
},
pairs = {
move = {'left', 'right', 'up', 'down'}
},
joystick = love.joystick.getJoysticks()[1],
}
function love.update(dt)
input:update()
local x, y = input:get 'move'
playerShip:move(x*100, y*100)
if input:pressed 'action' then
playerShip:shoot()
end
end
```
## Installation
To use Baton, place `baton.lua` in your project, and then add this code to your `main.lua`:
```lua
baton = require 'baton' -- if your baton.lua is in the root directory
baton = require 'path.to.baton' -- if it's in subfolders
```
## Usage
### Defining controls
Controls are defined using a table. Each key should be the name of a control, and each value should be another table. This table contains strings defining what sources should be mapped to the control. For example, this table
```lua
controls = {
left = {'key:left', 'key:a', 'axis:leftx-'}
shoot = {'key:x', 'button:a'}
}
```
will create a control called "left" that responds to the left arrow key, the A key, and pushing the left analog stick on the controller to the left, and a control called "shoot" that responds to the X key on the keyboard and the A button on the gamepad.
Sources are strings with the following format:
```lua
'[input type]:[input source]'
```
Here are the different input types and the sources that can be associated with them:
| Type | Description | Source |
| --------| -----------------------------| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `key` | A keyboard key. | Any LÖVE [KeyConstant](http://love2d.org/wiki/KeyConstant) |
| `sc` | A scancode. | Any LÖVE [KeyConstant](http://love2d.org/wiki/KeyConstant) |
| `mouse` | A mouse button. | A number representing a mouse button (see [love.mouse.isDown](https://love2d.org/wiki/love.mouse.isDown)) |
| `axis` | A joystick or gamepad axis. | Either a number representing a joystick axis or a LÖVE [GamepadAxis](http://love2d.org/wiki/GamepadAxis). Add a '+' or '-' on the end to denote the direction to detect.|
| `button`| A joystick or gamepad button.| Either a number representing a joystick button or a LÖVE [GamepadButton](http://love2d.org/wiki/GamepadButton) |
| `hat` | A joystick hat. | A number representing a joystick hat and a [JoystickHat](https://love2d.org/wiki/JoystickHat). For example '1r' corresponds to the 1st hat pushed right. |
### Defining axis pairs
Baton allows you to define **axis pairs**, which group four controls under a single name. This is perfect for analog sticks, arrow keys, etc., as it allows you to get x and y components quickly. Each pair is defined by a table with the names of the four controls (in the order left, right, up, down).
```lua
pairs = {
move = {'moveLeft', 'moveRight', 'moveUp', 'moveDown'},
aim = {'aimLeft', 'aimRight', 'aimUp', 'aimDown'},
}
```
### Players
**Players** are the objects that monitor and manage inputs.
#### Creating players
To create a player, use `baton.new`:
```lua
player = baton.new(config)
```
`config` is a table containing the following values:
- `controls` - a table of controls
- `pairs` - a table of axis pairs (optional)
- `joystick` - a LÖVE joystick (returned from `love.joystick.getJoysticks`). The `joystick` argument is optional; if it's not specified, or if the joystick becomes unavailable later, the player object will just ignore controller inputs.
- `deadzone` - a number from 0-1 representing the minimum value axes have to cross to be detected (optional)
- `squareDeadzone` (bool) - whether to use square deadzones for axis pairs or not (optional)
#### Updating players
You should update each player each frame by calling this function:
```lua
player:update()
```
#### Getting the value of controls
To get the value of a control, use:
```lua
value = player:get(control)
```
For example, for the controls defined above, we could get the value of the "left" control by doing
```lua
left = player:get 'left'
```
`player:get` always returns a number between 0 and 1, and as such, it is most applicable to controls that act as axes, such as movement controls. To get the value of a control without applying the deadzone, use `player:getRaw`.
#### Getting the value of axis pairs
`player.get` can also get the x and y components of an axis pair.
```lua
x, y = player:get(pair)
```
In this case, `x` and `y` are numbers between -1 and 1. The length of the vector x, y is capped to 1. `player.getRaw` will return the value of axis pairs without deadzone applied.
#### Getting down, pressed, and released states
To see whether a control is currently "held down", use:
```lua
down = player:down(control)
```
`player:down` returns `true` if the value of the control is greater than the deadzone, and `false` if not.
```lua
pressed = player:pressed(control)
```
`player:pressed` return `true` if the control was pressed this `frame`, and false if not.
```lua
released = player:released(control)
```
`player:released` return `true` if the control was released this `frame`, and false if not.
These functions are most applicable for controls that act as buttons, such as a shoot button. That being said, they can be used for any control, which is useful if you want to, for example, use a movement control as a discrete button press to operate a menu.
#### Updating the configuration
The `controls` table, `pairs` table, `joystick`, `deadzone`, and `squareDeadzone` can all be accessed via `player.config`. Any of the values can be changed, and the player's behavior will be updated automatically. Note, however, that new controls and pairs cannot be added after the player is created, and controls and pairs should not be removed entirely (if you want to disable a control, you can set it to an empty table, removing all of its sources).
#### Getting the active input device
At any time, only the keyboard/mouse sources or the joystick sources for a player will be active. A device will be considered active if any of the sources for that device exceed the deadzone. The keyboard and mouse will always take precedence over the joystick.
You can call `player:getActiveDevice()` to see which input device is currently active. It will return either `'kbm'` (keyboard/mouse) or `'joy'` (joystick) (or `'none'` if no sources have been used yet). This is useful if you need to change what you display on screen based on the controls the player is using (such as instructions).
## Contributing
Issues and pull requests are always welcome. To run the test, run `love .` in the baton folder.

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Semyon Entsov <swalrus@yandex.ru>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,94 +0,0 @@
# center
Center is a simple library that dynamically aligns content to the center of game window.
<img src="https://github.com/S-Walrus/center/blob/master/screenshots/center.png?raw=true" width="256">
## Setup
```lua
local center = require "center"
center:setupScreen(600, 400)
function love.draw()
center:start()
-- draw here
center:finish()
end
function love.resize(width, height)
center:resize(width, height)
end
```
## API
### setupScreen
```lua
center:setupScreen(width, height)
```
Initializes Center
### apply
```lua
center:apply()
```
Applies changes.
This function is responsible for the whole alignment process, so any function (except for `setupScreen` and `resize`) will not make sense without `apply()` after it.
### setMaxWidth
```lua
center:setMaxWidth(width)
```
Width of the content **won't be greater** than specified value (default value is 0, that means that there is no boundaries).
### setMaxHeight
```lua
center:setMaxHeight(height)
```
Works the same as the previous one.
### setMaxRelativeWidth
```lua
center:setMaxRelativeWidth(width)
```
The **relative** width of the content (actual width / available width) **won't be greater** than specified value (default value is 0, that means that there is no relative boundaries).
### setMaxRelativeHeight
```lua
center:setMaxRelativeHeight(height)
```
Works the same as the previous one.
### setBorders
```lua
center:setBorders(t, r, b, l)
```
Specify the available area for the content.
Each argument represents how far the content should be placed from each side from top to left clockwise.
The content is aligned to the center of **this** area.
*For better understanding, illustrations will be added soon*
### start, finish
```lua
function love.draw()
center:start()
-- draw here
center:finish()
end
```
This pair of functions is supposed to be called once inside `love.draw()`. Any rendering after `center.finish()` affect the default canvas, so you can draw UI or anything else.
### toGame
```lua
center:toGame(x, y)
```
Return position of a point in aligned coordinate system insead of default one.

View File

@ -1,119 +0,0 @@
--[[
MIT License
Copyright (c) 2019 Semyon Entsov <swalrus@yandex.ru>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
local center = {}
function center:setupScreen(width, height)
self._WIDTH = width
self._HEIGHT = height
self._MAX_WIDTH = 0
self._MAX_HEIGHT = 0
self._MAX_RELATIVE_WIDTH = 0
self._MAX_RELATIVE_HEIGHT = 0
self._SCREEN_WIDTH = love.graphics.getWidth()
self._SCREEN_HEIGHT = love.graphics.getHeight()
self._BORDERS = {
['t'] = 0,
['r'] = 0,
['b'] = 0,
['l'] = 0
}
self:apply()
return self
end
function center:setBorders(top, right, bottom, left)
self._BORDERS.t = top
self._BORDERS.r = right
self._BORDERS.b = bottom
self._BORDERS.l = left
end
function center:setMaxWidth(width)
self._MAX_WIDTH = width
end
function center:setMaxHeight(height)
self._MAX_HEIGHT = height
end
function center:setMaxRelativeWidth(width)
self._MAX_RELATIVE_WIDTH = width
end
function center:setMaxRelativeHeight(height)
self._MAX_RELATIVE_HEIGHT = height
end
function center:resize(width, height)
self._SCREEN_WIDTH = width
self._SCREEN_HEIGHT = height
self:apply()
end
function center:apply()
local available_width = self._SCREEN_WIDTH - self._BORDERS.l - self._BORDERS.r
local available_height = self._SCREEN_HEIGHT - self._BORDERS.t - self._BORDERS.b
local max_width = available_width
local max_height = available_height
if self._MAX_RELATIVE_WIDTH > 0 and available_width * self._MAX_RELATIVE_WIDTH < max_width then
max_width = available_width * self._MAX_RELATIVE_WIDTH
end
if self._MAX_RELATIVE_HEIGHT > 0 and available_height * self._MAX_RELATIVE_HEIGHT < max_height then
max_height = available_height * self._MAX_RELATIVE_HEIGHT
end
if self._MAX_WIDTH > 0 and self._MAX_WIDTH < max_width then
max_width = self._MAX_WIDTH
end
if self._MAX_HEIGHT > 0 and self._MAX_HEIGHT < max_height then
max_height = self._MAX_HEIGHT
end
if max_height / max_width > self._HEIGHT / self._WIDTH then
self._CANVAS_WIDTH = max_width
self._CANVAS_HEIGHT = self._CANVAS_WIDTH * (self._HEIGHT / self._WIDTH)
else
self._CANVAS_HEIGHT = max_height
self._CANVAS_WIDTH = self._CANVAS_HEIGHT * (self._WIDTH / self._HEIGHT)
end
self._SCALE = self._CANVAS_HEIGHT / self._HEIGHT
self._OFFSET_X = self._BORDERS.l + (available_width - self._CANVAS_WIDTH) / 2
self._OFFSET_Y = self._BORDERS.t + (available_height - self._CANVAS_HEIGHT) / 2
end
function center:start()
love.graphics.push()
love.graphics.translate(self._OFFSET_X, self._OFFSET_Y)
love.graphics.scale(self._SCALE, self._SCALE)
end
function center:finish()
love.graphics.pop()
end
function center:toGame(x, y)
return (x - self._OFFSET_X) / self._SCALE,
(y - self._OFFSET_Y) / self._SCALE
end
return center

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,36 @@
language: python
sudo: false
env:
- LUA="lua=5.1"
- LUA="lua=5.2"
- LUA="lua=5.3"
- LUA="luajit=2.0"
- LUA="luajit=2.1"
before_install:
- pip install hererocks
- hererocks lua_install -r^ --$LUA
- export PATH=$PATH:$PWD/lua_install/bin # Add directory with all installed binaries to PATH
install:
- luarocks install luacheck
- luarocks install busted
- luarocks install luacov
- luarocks install luacov-coveralls
script:
- luacheck --no-unused-args --std max+busted *.lua spec
- busted --verbose --coverage
after_success:
- luacov-coveralls --exclude $TRAVIS_BUILD_DIR/lua_install
branches:
except:
- gh-pages
notifications:
email:
on_success: change
on_failure: always

View File

@ -0,0 +1,55 @@
middleclass changelog
====================
# Version 4.1.1
* Fixed a bug in which `static` values which evaluated to `false` were not available
in subclasses (#51, thanks @qaisjp for the patch!)
* `isInstanceOf` does not throw an error any more when its first parameter is a
primitive (#55) (This effectively undoes the change introduced in 4.1.0)
# Version 4.1.0
* Simplifies implementation of `isInstanceOf` and `isSubclassOf`. They will now raise an error if their first
parameter (the `self`) isn't an instance or a class respectively.
# Version 4.0.0
* Unified the method and metamethod lookup into a single algorithm
* Added the capacity of setting up the `__index` metamethod in classes
* Removed global `Object` (classes created with `class(<name>)` have no superclass now)
* Removed default method `Class:implements(<mixin>)`
* Renamed several internal functions
# Version 3.2.0
* Changed the way metamethods were handled to fix certain bugs (un-stubbed metamethods could not be inherited)
# Version 3.1.0
* Added Lua 5.3 metamethod support (`__band`, `__bor`, `__bxor`, `__shl`, `__bnot`)
# Version 3.0.1
* Added `__len`, `__ipairs` and `__pairs` metamethods for Lua 5.2
# Version 3.0
* Anything that behaves reasonably like a class can be a class (no internal list of classes)
* The `class` global function is now just the return value of `require
'middleclass'`. It is a callable table, but works exactly as before.
* The global variable `Object` becomes `class.Object`
* The global function `instanceOf` becomes `class.Object.isInstanceOf`. Parameter order is reversed.
* The global function `subclassOf` becomes `class.Object.static.isSubclassOf`. Parameter order is reversed.
* The global function `implements` becomes `class.Object.static.implements`. Parameter order is reversed.
* Specs have been translated from telescope to busted
# Version 2.0
* Static methods are now separated from instance methods
* class.superclass has now become class.super
* It's now possible to do class.subclasses
* middleclass is now a single file; init.lua has dissapeared
* license is changed from BSD to MIT. License included in source FTW

View File

@ -0,0 +1,20 @@
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

80
lib/middleclass/README.md Normal file
View File

@ -0,0 +1,80 @@
middleclass
===========
[![Build Status](https://travis-ci.org/kikito/middleclass.png?branch=master)](https://travis-ci.org/kikito/middleclass)
[![Coverage Status](https://coveralls.io/repos/kikito/middleclass/badge.svg?branch=master&service=github)](https://coveralls.io/github/kikito/middleclass?branch=master)
A simple OOP library for Lua. It has inheritance, metamethods (operators), class variables and weak mixin support.
Quick Look
==========
```lua
local class = require 'middleclass'
local Fruit = class('Fruit') -- 'Fruit' is the class' name
function Fruit:initialize(sweetness)
self.sweetness = sweetness
end
Fruit.static.sweetness_threshold = 5 -- class variable (also admits methods)
function Fruit:isSweet()
return self.sweetness > Fruit.sweetness_threshold
end
local Lemon = class('Lemon', Fruit) -- subclassing
function Lemon:initialize()
Fruit.initialize(self, 1) -- invoking the superclass' initializer
end
local lemon = Lemon:new()
print(lemon:isSweet()) -- false
```
Documentation
=============
See the [github wiki page](https://github.com/kikito/middleclass/wiki) for examples & documentation.
You can read the `CHANGELOG.md` file to see what has changed on each version of this library.
If you need help updating to a new middleclass version, read `UPDATING.md`.
Installation
============
Just copy the middleclass.lua file wherever you want it (for example on a lib/ folder). Then write this in any Lua file where you want to use it:
```lua
local class = require 'middleclass'
```
Specs
=====
This project uses [busted](http://olivinelabs.com/busted/) for its specs. If you want to run the specs, you will have to install it first. Then just execute the following:
```bash
cd /folder/where/the/spec/folder/is
busted
```
Performance tests
=================
Middleclass also comes with a small performance test suite. Just run the following command:
```bash
lua performance/run.lua
```
License
=======
Middleclass is distributed under the MIT license.

View File

@ -0,0 +1,69 @@
Updating from 3.x to 4.x
========================
In middleclass 4.0 there is no global `Object` class any more. Classes created with `class(<name>)` don't have a superclass any more.
If you need a global `Object` class, you must create it explicitly and then use it when creating new classes:
```lua
local Object = class('Object')
...
local MyClass = class('MyClass', Object)
```
If you are using a library which depends on the internal implementation of middleclass they might not work with middleclass 4.0. You might need to update those other libraries.
Middleclass 4.0 comes with support for `__index` metamethod support. If your library manipulated the classes' `__instanceDict` internal attribute, you might do the same thing now using `__index` instead.
Also note that the class method `:implements` has been removed.
Updating from 2.x to 3.x
========================
Middleclass used to expose several global variables on the main scope. It does not do that anymore.
`class` is now returned by `require 'middleclass'`, and it is not set globally. So you can do this:
```lua
local class = require 'middleclass'
local MyClass = class('MyClass') -- works as before
```
`Object` is not a global variable any more. But you can get it from `class.Object`
```lua
local class = require 'middleclass'
local Object = class.Object
print(Object) -- prints 'class Object'
```
The public functions `instanceOf`, `subclassOf` and `includes` have been replaced by `Object.isInstanceOf`, `Object.static.isSubclassOf` and `Object.static.includes`.
Prior to 3.x:
```lua
instanceOf(MyClass, obj)
subclassOf(Object, aClass)
includes(aMixin, aClass)
```
Since 3.x:
```lua
obj:isInstanceOf(MyClass)
aClass:isSubclassOf(Object)
aClass:includes(aMixin)
```
The 3.x code snippet will throw an error if `obj` is not an object, or if `aClass` is not a class (since they will not implement `isInstanceOf`, `isSubclassOf` or `includes`).
If you are unsure of whether `obj` and `aClass` are an object or a class, you can use the methods in `Object`. They are prepared to work with random types, not just classes and instances:
```lua
Object.isInstanceOf(obj, MyClass)
Object.isSubclassOf(aClass, Object)
Object.includes(aClass, aMixin)
```
Notice that the parameter order is not the same now as it was in 2.x. Also note the change in naming: `isInstanceOf` instead of `instanceOf`, and `isSubclassOf` instead of `subclassOf`.

183
lib/middleclass/init.lua Normal file
View File

@ -0,0 +1,183 @@
local middleclass = {
_VERSION = 'middleclass v4.1.1',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
else
return f[name]
end
end
end
end
local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end
local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f
if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end
_propagateInstanceMethod(aClass, name, f)
end
local function _tostring(self) return "class " .. self.name end
local function _call(self, ...) return self:new(...) end
local function _createClass(name, super)
local dict = {}
dict.__index = dict
local aClass = { name = name, super = super, static = {},
__instanceDict = dict, __declaredMethods = {},
subclasses = setmetatable({}, {__mode='k'}) }
if super then
setmetatable(aClass.static, {
__index = function(_,k)
local result = rawget(dict,k)
if result == nil then
return super.static[k]
end
return result
end
})
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })
return aClass
end
local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then aClass[name] = method end
end
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end
if type(mixin.included)=="function" then mixin:included(aClass) end
return aClass
end
local DefaultMixin = {
__tostring = function(self) return "instance of " .. tostring(self.class) end,
initialize = function(self, ...) end,
isInstanceOf = function(self, aClass)
return type(aClass) == 'table'
and type(self) == 'table'
and (self.class == aClass
or type(self.class) == 'table'
and type(self.class.isSubclassOf) == 'function'
and self.class:isSubclassOf(aClass))
end,
static = {
allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
new = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = self:allocate()
instance:initialize(...)
return instance
end,
subclass = function(self, name)
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
for methodName, f in pairs(self.__instanceDict) do
_propagateInstanceMethod(subclass, methodName, f)
end
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end,
subclassed = function(self, other) end,
isSubclassOf = function(self, other)
return type(other) == 'table' and
type(self.super) == 'table' and
( self.super == other or self.super:isSubclassOf(other) )
end,
include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
}
}
function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
return middleclass

View File

@ -0,0 +1,43 @@
local class = require 'middleclass'
time = require 'performance/time'
time('class creation', function()
local A = class('A')
end)
local A = class('A')
time('instance creation', function()
local a = A:new()
end)
function A:foo()
return 1
end
local a = A:new()
time('instance method invocation', function()
a:foo()
end)
local B = class('B', A)
local b = B:new()
time('inherited method invocation', function()
b:foo()
end)
function A.static:bar()
return 2
end
time('class method invocation', function()
A:bar()
end)
time('inherited class method invocation', function()
B:bar()
end)

View File

@ -0,0 +1,13 @@
return function(title, f)
collectgarbage()
local startTime = os.clock()
for i=0,10000 do f() end
local endTime = os.clock()
print( title, endTime - startTime )
end

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "3.0-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v3.0.0.tar.gz",
dir = "middleclass-3.0.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "3.1-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v3.1.0.tar.gz",
dir = "middleclass-3.1.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "3.2-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v3.2.0.tar.gz",
dir = "middleclass-3.2.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "4.0-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v4.0.0.tar.gz",
dir = "middleclass-4.0.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "4.1-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v4.1.0.tar.gz",
dir = "middleclass-4.1.0"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,21 @@
package = "middleclass"
version = "4.1.1-0"
source = {
url = "https://github.com/kikito/middleclass/archive/v4.1.1.tar.gz",
dir = "middleclass-4.1.1"
}
description = {
summary = "A simple OOP library for Lua",
detailed = "It has inheritance, metamethods (operators), class variables and weak mixin support",
homepage = "https://github.com/kikito/middleclass",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
middleclass = "middleclass.lua"
}
}

View File

@ -0,0 +1,28 @@
local class = require 'middleclass'
describe('class()', function()
describe('when given no params', function()
it('it throws an error', function()
assert.error(class)
end)
end)
describe('when given a name', function()
it('the resulting class has the correct name and Object as its superclass', function()
local TheClass = class('TheClass')
assert.equal(TheClass.name, 'TheClass')
assert.is_nil(TheClass.super)
end)
end)
describe('when given a name and a superclass', function()
it('the resulting class has the correct name and superclass', function()
local TheSuperClass = class('TheSuperClass')
local TheSubClass = class('TheSubClass', TheSuperClass)
assert.equal(TheSubClass.name, 'TheSubClass')
assert.equal(TheSubClass.super, TheSuperClass)
end)
end)
end)

View File

@ -0,0 +1,138 @@
local class = require 'middleclass'
describe('A Class', function()
describe('Default stuff', function()
local AClass
before_each(function()
AClass = class('AClass')
end)
describe('name', function()
it('is correctly set', function()
assert.equal(AClass.name, 'AClass')
end)
end)
describe('tostring', function()
it('returns "class *name*"', function()
assert.equal(tostring(AClass), 'class AClass')
end)
end)
describe('()', function()
it('returns an object, like Class:new()', function()
local obj = AClass()
assert.equal(obj.class, AClass)
end)
end)
describe('include', function()
it('throws an error when used without the :', function()
assert.error(function() AClass.include() end)
end)
it('throws an error when passed a non-table:', function()
assert.error(function() AClass:include(1) end)
end)
end)
describe('subclass', function()
it('throws an error when used without the :', function()
assert.error(function() AClass.subclass() end)
end)
it('throws an error when no name is given', function()
assert.error( function() AClass:subclass() end)
end)
describe('when given a subclass name', function()
local SubClass
before_each(function()
function AClass.static:subclassed(other) self.static.child = other end
SubClass = AClass:subclass('SubClass')
end)
it('it returns a class with the correct name', function()
assert.equal(SubClass.name, 'SubClass')
end)
it('it returns a class with the correct superclass', function()
assert.equal(SubClass.super, AClass)
end)
it('it invokes the subclassed hook method', function()
assert.equal(SubClass, AClass.child)
end)
it('it includes the subclass in the list of subclasses', function()
assert.is_true(AClass.subclasses[SubClass])
end)
end)
end)
end)
describe('attributes', function()
local A, B
before_each(function()
A = class('A')
A.static.foo = 'foo'
B = class('B', A)
end)
it('are available after being initialized', function()
assert.equal(A.foo, 'foo')
end)
it('are available for subclasses', function()
assert.equal(B.foo, 'foo')
end)
it('are overridable by subclasses, without affecting the superclasses', function()
B.static.foo = 'chunky bacon'
assert.equal(B.foo, 'chunky bacon')
assert.equal(A.foo, 'foo')
end)
end)
describe('methods', function()
local A, B
before_each(function()
A = class('A')
function A.static:foo() return 'foo' end
B = class('B', A)
end)
it('are available after being initialized', function()
assert.equal(A:foo(), 'foo')
end)
it('are available for subclasses', function()
assert.equal(B:foo(), 'foo')
end)
it('are overridable by subclasses, without affecting the superclasses', function()
function B.static:foo() return 'chunky bacon' end
assert.equal(B:foo(), 'chunky bacon')
assert.equal(A:foo(), 'foo')
end)
end)
end)

View File

@ -0,0 +1,236 @@
local class = require 'middleclass'
describe('Default methods', function()
local Object
before_each(function()
Object = class('Object')
end)
describe('name', function()
it('is correctly set', function()
assert.equal(Object.name, 'Object')
end)
end)
describe('tostring', function()
it('returns "class Object"', function()
assert.equal(tostring(Object), 'class Object')
end)
end)
describe('()', function()
it('returns an object, like Object:new()', function()
local obj = Object()
assert.is_true(obj:isInstanceOf(Object))
end)
end)
describe('subclass', function()
it('throws an error when used without the :', function()
assert.error(function() Object.subclass() end)
end)
it('throws an error when no name is given', function()
assert.error( function() Object:subclass() end)
end)
describe('when given a class name', function()
local SubClass
before_each(function()
SubClass = Object:subclass('SubClass')
end)
it('it returns a class with the correct name', function()
assert.equal(SubClass.name, 'SubClass')
end)
it('it returns a class with the correct superclass', function()
assert.equal(SubClass.super, Object)
end)
it('it includes the subclass in the list of subclasses', function()
assert.is_true(Object.subclasses[SubClass])
end)
end)
end)
describe('instance creation', function()
local SubClass
before_each(function()
SubClass = class('SubClass')
function SubClass:initialize() self.mark=true end
end)
describe('allocate', function()
it('allocates instances properly', function()
local instance = SubClass:allocate()
assert.equal(instance.class, SubClass)
assert.equal(tostring(instance), "instance of " .. tostring(SubClass))
end)
it('throws an error when used without the :', function()
assert.error(Object.allocate)
end)
it('does not call the initializer', function()
local allocated = SubClass:allocate()
assert.is_nil(allocated.mark)
end)
it('can be overriden', function()
local previousAllocate = SubClass.static.allocate
function SubClass.static:allocate()
local instance = previousAllocate(SubClass)
instance.mark = true
return instance
end
local allocated = SubClass:allocate()
assert.is_true(allocated.mark)
end)
end)
describe('new', function()
it('initializes instances properly', function()
local instance = SubClass:new()
assert.equal(instance.class, SubClass)
end)
it('throws an error when used without the :', function()
assert.error(SubClass.new)
end)
it('calls the initializer', function()
local initialized = SubClass:new()
assert.is_true(initialized.mark)
end)
end)
describe('isInstanceOf', function()
describe('primitives', function()
local o = Object:new()
local primitives = {nil, 1, 'hello', {}, function() end, Object:new()}
describe('used as classes', function()
for _,primitive in pairs(primitives) do
local theType = type(primitive)
it('object:isInstanceOf(, '.. theType ..') returns false', function()
assert.is_falsy(o:isInstanceOf(primitive))
end)
end
end)
describe('used as instances', function()
for _,primitive in pairs(primitives) do
local theType = type(primitive)
it('Object.isInstanceOf('.. theType ..', Object) returns false without error', function()
assert.is_falsy(Object.isInstanceOf(primitive, Object))
end)
end
end)
end)
describe('An instance', function()
local Class1 = class('Class1')
local Class2 = class('Class2', Class1)
local Class3 = class('Class3', Class2)
local UnrelatedClass = class('Unrelated')
local o1, o2, o3 = Class1:new(), Class2:new(), Class3:new()
it('isInstanceOf its class', function()
assert.is_true(o1:isInstanceOf(Class1))
assert.is_true(o2:isInstanceOf(Class2))
assert.is_true(o3:isInstanceOf(Class3))
end)
it('is instanceOf its class\' superclasses', function()
assert.is_true(o2:isInstanceOf(Class1))
assert.is_true(o3:isInstanceOf(Class1))
assert.is_true(o3:isInstanceOf(Class2))
end)
it('is not instanceOf its class\' subclasses', function()
assert.is_false(o1:isInstanceOf(Class2))
assert.is_false(o1:isInstanceOf(Class3))
assert.is_false(o2:isInstanceOf(Class3))
end)
it('is not instanceOf an unrelated class', function()
assert.is_false(o1:isInstanceOf(UnrelatedClass))
assert.is_false(o2:isInstanceOf(UnrelatedClass))
assert.is_false(o3:isInstanceOf(UnrelatedClass))
end)
end)
end)
end)
describe('isSubclassOf', function()
it('returns false for instances', function()
assert.is_false(Object:isSubclassOf(Object:new()))
end)
describe('on primitives', function()
local primitives = {nil, 1, 'hello', {}, function() end}
for _,primitive in pairs(primitives) do
local theType = type(primitive)
it('returns false for ' .. theType, function()
assert.is_false(Object:isSubclassOf(primitive))
end)
end
end)
describe('Any class (except Object)', function()
local Class1 = class('Class1')
local Class2 = class('Class2', Class1)
local Class3 = class('Class3', Class2)
local UnrelatedClass = class('Unrelated')
it('is subclassOf its direct superclass', function()
assert.is_true(Class2:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class2))
end)
it('is subclassOf its ancestors', function()
assert.is_true(Class3:isSubclassOf(Class1))
end)
it('is a subclassOf its class\' subclasses', function()
assert.is_true(Class2:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class1))
assert.is_true(Class3:isSubclassOf(Class2))
end)
it('is not a subclassOf an unrelated class', function()
assert.is_false(Class1:isSubclassOf(UnrelatedClass))
assert.is_false(Class2:isSubclassOf(UnrelatedClass))
assert.is_false(Class3:isSubclassOf(UnrelatedClass))
end)
end)
end)
end)

View File

@ -0,0 +1,65 @@
local class = require 'middleclass'
describe('An instance', function()
describe('attributes', function()
local Person
before_each(function()
Person = class('Person')
function Person:initialize(name)
self.name = name
end
end)
it('are available in the instance after being initialized', function()
local bob = Person:new('bob')
assert.equal(bob.name, 'bob')
end)
it('are available in the instance after being initialized by a superclass', function()
local AgedPerson = class('AgedPerson', Person)
function AgedPerson:initialize(name, age)
Person.initialize(self, name)
self.age = age
end
local pete = AgedPerson:new('pete', 31)
assert.equal(pete.name, 'pete')
assert.equal(pete.age, 31)
end)
end)
describe('methods', function()
local A, B, a, b
before_each(function()
A = class('A')
function A:overridden() return 'foo' end
function A:regular() return 'regular' end
B = class('B', A)
function B:overridden() return 'bar' end
a = A:new()
b = B:new()
end)
it('are available for any instance', function()
assert.equal(a:overridden(), 'foo')
end)
it('are inheritable', function()
assert.equal(b:regular(), 'regular')
end)
it('are overridable', function()
assert.equal(b:overridden(), 'bar')
end)
end)
end)

View File

@ -0,0 +1,85 @@
local class = require 'middleclass'
local it = require('busted').it
local describe = require('busted').describe
local before_each = require('busted').before_each
local assert = require('busted').assert
describe('Lua 5.2 Metamethods', function()
local Vector, v
before_each(function()
Vector= class('Vector')
function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end
function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end
function Vector.__len(a) return 3 end
function Vector.__pairs(a)
local t = {x=a.x,y=a.y,z=a.z}
return coroutine.wrap(function()
for k,val in pairs(t) do
coroutine.yield(k,val)
end
end)
end
function Vector.__ipairs(a)
local t = {a.x,a.y,a.z}
return coroutine.wrap(function()
for k,val in ipairs(t) do
coroutine.yield(k,val)
end
end)
end
v = Vector:new(1,2,3)
end)
it('implements __len', function()
assert.equal(#v, 3)
end)
it('implements __pairs',function()
local output = {}
for k,val in pairs(v) do
output[k] = val
end
assert.are.same(output,{x=1,y=2,z=3})
end)
it('implements __ipairs',function()
local output = {}
for _,i in ipairs(v) do
output[#output+1] = i
end
assert.are.same(output,{1,2,3})
end)
describe('Inherited Metamethods', function()
local Vector2, v2
before_each(function()
Vector2= class('Vector2', Vector)
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
v2 = Vector2:new(1,2,3)
end)
it('implements __len', function()
assert.equal(#v2, 3)
end)
it('implements __pairs',function()
local output = {}
for k,val in pairs(v2) do
output[k] = val
end
assert.are.same(output,{x=1,y=2,z=3})
end)
it('implements __ipairs',function()
local output = {}
for _,i in ipairs(v2) do
output[#output+1] = i
end
assert.are.same(output,{1,2,3})
end)
end)
end)

View File

@ -0,0 +1,106 @@
local class = require 'middleclass'
local it = require('busted').it
local describe = require('busted').describe
local before_each = require('busted').before_each
local assert = require('busted').assert
describe('Lua 5.3 Metamethods', function()
local Vector, v, last_gc
before_each(function()
Vector= class('Vector')
function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end
function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end
function Vector.__pairs(a)
local t = {x=a.x,y=a.y,z=a.z}
return coroutine.wrap(function()
for k,val in pairs(t) do
coroutine.yield(k,val)
end
end)
end
function Vector.__len(a) return 3 end
function Vector.__gc(a) last_gc = {a.class.name, a.x, a.y, a.z} end
function Vector.__band(a,n) return a.class:new(a.x & n, a.y & n, a.z & n) end
function Vector.__bor(a,n) return a.class:new(a.x | n, a.y | n, a.z | n) end
function Vector.__bxor(a,n) return a.class:new(a.x ~ n, a.y ~ n, a.z ~ n) end
function Vector.__shl(a,n) return a.class:new(a.x << n, a.y << n, a.z << n) end
function Vector.__shr(a,n) return a.class:new(a.x >> n, a.y >> n, a.z >> n) end
function Vector.__bnot(a) return a.class:new(~a.x, ~a.y, ~a.z) end
v = Vector:new(1,2,3)
end)
it('implements __gc', function()
collectgarbage()
v = nil
collectgarbage()
assert.are.same(last_gc, {"Vector",1,2,3})
end)
it('implements __band', function()
assert.equal(v & 1, Vector(1,0,1))
end)
it('implements __bor', function()
assert.equal(v | 0, Vector(1,2,3))
end)
it('implements __bxor', function()
assert.equal(v | 1, Vector(1,3,3))
end)
it('implements __shl', function()
assert.equal(v << 1, Vector(2,4,6))
end)
it('implements __shr', function()
assert.equal(v >> 1, Vector(0,1,1))
end)
it('implements __bnot', function()
assert.equal(~v, Vector(-2,-3,-4))
end)
describe('Inherited Metamethods', function()
local Vector2, v2
before_each(function()
Vector2= class('Vector2', Vector)
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
v2 = Vector2:new(1,2,3)
end)
it('implements __gc', function()
collectgarbage()
v2 = nil
collectgarbage()
assert.are.same(last_gc, {"Vector2",1,2,3})
end)
it('implements __band', function()
assert.equal(v2 & 1, Vector2(1,0,1))
end)
it('implements __bor', function()
assert.equal(v2 | 0, Vector2(1,2,3))
end)
it('implements __bxor', function()
assert.equal(v2 | 1, Vector2(1,3,3))
end)
it('implements __shl', function()
assert.equal(v2 << 1, Vector2(2,4,6))
end)
it('implements __shr', function()
assert.equal(v2 >> 1, Vector2(0,1,1))
end)
it('implements __bnot', function()
assert.equal(~v2, Vector2(-2,-3,-4))
end)
end)
end)

View File

@ -0,0 +1,317 @@
local class = require 'middleclass'
local function is_lua_5_2_compatible()
return type(rawlen) == 'function'
end
local function is_lua_5_3_compatible()
return type(string.unpack) == 'function'
end
if is_lua_5_2_compatible() then
require 'spec/metamethods_lua_5_2'
end
if is_lua_5_3_compatible() then
require 'spec.metamethods_lua_5_3'
end
describe('Metamethods', function()
describe('Custom Metamethods', function()
local Vector, v, w
before_each(function()
Vector= class('Vector')
function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end
function Vector.__tostring(a) return a.class.name .. '[' .. a.x .. ',' .. a.y .. ',' .. a.z .. ']' end
function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end
function Vector.__lt(a,b) return a() < b() end
function Vector.__le(a,b) return a() <= b() end
function Vector.__add(a,b) return a.class:new(a.x+b.x, a.y+b.y ,a.z+b.z) end
function Vector.__sub(a,b) return a.class:new(a.x-b.x, a.y-b.y, a.z-b.z) end
function Vector.__div(a,s) return a.class:new(a.x/s, a.y/s, a.z/s) end
function Vector.__unm(a) return a.class:new(-a.x, -a.y, -a.z) end
function Vector.__concat(a,b) return a.x*b.x+a.y*b.y+a.z*b.z end
function Vector.__call(a) return math.sqrt(a.x*a.x+a.y*a.y+a.z*a.z) end
function Vector.__pow(a,b)
return a.class:new(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x)
end
function Vector.__mul(a,b)
if type(b)=="number" then return a.class:new(a.x*b, a.y*b, a.z*b) end
if type(a)=="number" then return b.class:new(a*b.x, a*b.y, a*b.z) end
end
Vector.__metatable = "metatable of a vector"
Vector.__mode = "k"
v = Vector:new(1,2,3)
w = Vector:new(2,4,6)
end)
it('implements __tostring', function()
assert.equal(tostring(v), "Vector[1,2,3]")
end)
it('implements __eq', function()
assert.equal(v, v)
end)
it('implements __lt', function()
assert.is_true(v < w)
end)
it('implements __le', function()
assert.is_true(v <= w)
end)
it('implements __add', function()
assert.equal(v+w, Vector(3,6,9))
end)
it('implements __sub', function()
assert.equal(w-v, Vector(1,2,3))
end)
it('implements __div', function()
assert.equal(w/2, Vector(1,2,3))
end)
it('implements __concat', function()
assert.equal(v..w, 28)
end)
it('implements __call', function()
assert.equal(v(), math.sqrt(14))
end)
it('implements __pow', function()
assert.equal(v^w, Vector(0,0,0))
end)
it('implements __mul', function()
assert.equal(4*v, Vector(4,8,12))
end)
it('implements __metatable', function()
assert.equal("metatable of a vector", getmetatable(v))
end)
it('implements __mode', function()
v[{}] = true
collectgarbage()
for k in pairs(v) do assert.not_table(k) end
end)
--[[
it('implements __index', function()
assert.equal(b[1], 3)
end)
--]]
describe('Inherited Metamethods', function()
local Vector2, v2, w2
before_each(function()
Vector2= class('Vector2', Vector)
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
v2 = Vector2:new(1,2,3)
w2 = Vector2:new(2,4,6)
end)
it('implements __tostring', function()
assert.equal(tostring(v2), "Vector2[1,2,3]")
end)
it('implements __eq', function()
assert.equal(v2, v2)
end)
it('implements __lt', function()
assert.is_true(v2 < w2)
end)
it('implements __le', function()
assert.is_true(v2 <= w2)
end)
it('implements __add', function()
assert.equal(v2+w2, Vector2(3,6,9))
end)
it('implements __sub', function()
assert.equal(w2-v2, Vector2(1,2,3))
end)
it('implements __div', function()
assert.equal(w2/2, Vector2(1,2,3))
end)
it('implements __concat', function()
assert.equal(v2..w2, 28)
end)
it('implements __call', function()
assert.equal(v2(), math.sqrt(14))
end)
it('implements __pow', function()
assert.equal(v2^w2, Vector2(0,0,0))
end)
it('implements __mul', function()
assert.equal(4*v2, Vector2(4,8,12))
end)
it('implements __metatable', function()
assert.equal("metatable of a vector", getmetatable(v2))
end)
it('implements __mode', function()
v2[{}] = true
collectgarbage()
for k in pairs(v2) do assert.not_table(k) end
end)
it('allows inheriting further', function()
local Vector3 = class('Vector3', Vector2)
local v3 = Vector3(1,2,3)
local w3 = Vector3(3,4,5)
assert.equal(v3+w3, Vector3(4,6,8))
end)
describe('Updates', function()
it('overrides __add', function()
Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end
assert.equal(v2+w2, Vector2(1.5,3,4.5))
end)
it('updates __add', function()
Vector.__add = Vector.__sub
assert.equal(v2+w2, Vector2(-1,-2,-3))
end)
it('does not update __add after overriding', function()
Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end
Vector.__add = Vector.__sub
assert.equal(v2+w2, Vector2(-0.5,-1,-1.5))
end)
it('reverts __add override', function()
Vector2.__add = function(a, b) return Vector.__add(a, b)/2 end
Vector2.__add = nil
assert.equal(v2+w2, Vector2(3,6,9))
end)
end)
end)
end)
describe('Custom __index and __newindex', function()
describe('Tables', function()
local Proxy, fallback, p
before_each(function()
Proxy = class('Proxy')
fallback = {foo = 'bar', common = 'fallback'}
Proxy.__index = fallback
Proxy.__newindex = fallback
Proxy.common = 'class'
p = Proxy()
end)
it('uses __index', function()
assert.equal(p.foo, 'bar')
end)
it('does not use __index when field exists in class', function()
assert.equal(p.common, 'class')
end)
it('uses __newindex', function()
p.key = 'value'
assert.equal(fallback.key, 'value')
end)
it('uses __newindex when field exists in class', function()
p.common = 'value'
assert.equal(p.common, 'class')
assert.equal(Proxy.common, 'class')
assert.equal(fallback.common, 'value')
end)
end)
describe('Functions', function()
local Namespace, Rectangle, r
before_each(function()
Namespace = class('Namespace')
function Namespace:__index(name)
local getter = self.class[name.."Getter"]
if getter then return getter(self) end
end
function Namespace:__newindex(name, value)
local setter = self.class[name.."Setter"]
if setter then setter(self, value) else rawset(self, name, value) end
end
Rectangle = class('Rectangle', Namespace)
function Rectangle:initialize(x, y, scale)
self._scale, self.x, self.y = 1, x, y
self.scale = scale
end
function Rectangle:scaleGetter() return self._scale end
function Rectangle:scaleSetter(v)
self.x = self.x*v/self._scale
self.y = self.y*v/self._scale
self._scale = v
end
function Rectangle:areaGetter() return self.x * self.y end
r = Rectangle(3, 4, 2)
end)
it('uses setter', function()
assert.equal(r.x, 6)
assert.equal(r.y, 8)
r.scale = 3
assert.equal(r.x, 9)
assert.equal(r.y, 12)
end)
it('uses getters', function()
assert.equal(r.scale, 2)
assert.equal(r.area, 48)
end)
it('updates inherited __index', function()
function Namespace.__index() return 42 end
assert.equal(r.area, 42)
function Rectangle.__index() return 24 end
assert.equal(r.area, 24)
function Namespace.__index() return 96 end
assert.equal(r.area, 24)
Rectangle.__index = nil
assert.equal(r.area, 96)
end)
end)
end)
describe('Default Metamethods', function()
local Peter, peter
before_each(function()
Peter = class('Peter')
peter = Peter()
end)
describe('A Class', function()
it('has a call metamethod properly set', function()
assert.is_true(peter:isInstanceOf(Peter))
end)
it('has a tostring metamethod properly set', function()
assert.equal(tostring(Peter), 'class Peter')
end)
end)
describe('An instance', function()
it('has a tostring metamethod, returning a different result from Object.__tostring', function()
assert.equal(tostring(peter), 'instance of class Peter')
end)
end)
end)
end)

View File

@ -0,0 +1,53 @@
local class = require 'middleclass'
describe('A Mixin', function()
local Mixin1, Mixin2, Class1, Class2
before_each(function()
Mixin1, Mixin2 = {},{}
function Mixin1:included(theClass) theClass.includesMixin1 = true end
function Mixin1:foo() return 'foo' end
function Mixin1:bar() return 'bar' end
Mixin1.static = {}
Mixin1.static.bazzz = function() return 'bazzz' end
function Mixin2:baz() return 'baz' end
Class1 = class('Class1'):include(Mixin1, Mixin2)
function Class1:foo() return 'foo1' end
Class2 = class('Class2', Class1)
function Class2:bar2() return 'bar2' end
end)
it('invokes the "included" method when included', function()
assert.is_true(Class1.includesMixin1)
end)
it('has all its functions (except "included") copied to its target class', function()
assert.equal(Class1:bar(), 'bar')
assert.is_nil(Class1.included)
end)
it('makes its functions available to subclasses', function()
assert.equal(Class2:baz(), 'baz')
end)
it('allows overriding of methods in the same class', function()
assert.equal(Class2:foo(), 'foo1')
end)
it('allows overriding of methods on subclasses', function()
assert.equal(Class2:bar2(), 'bar2')
end)
it('makes new static methods available in classes', function()
assert.equal(Class1:bazzz(), 'bazzz')
assert.equal(Class2:bazzz(), 'bazzz')
end)
end)

295
main.lua
View File

@ -1,4 +1,5 @@
animx = require "lib/animx"
class = require "lib/middleclass"
----------------------------------------
-- LOAD
@ -7,6 +8,157 @@ function love.load ()
left = 0; right = 1; up = 2; down = 3
upleft = 4; downleft = 5; upright = 6; downright = 7
player = Bat:new( )
--
-- for compliance with Statute 43.5 (2019); all birds must report births to local Officials
birdRegistry = {}
end
----------------------------------------
-- UPDATE
----------------------------------------
function love.update ( dt )
-- player.x = player.x + 100 * dt
player:update( dt )
animx.update(dt)
end
----------------------------------------
-- DRAW
----------------------------------------
function love.draw ()
love.graphics.print('Hello World!', 400, 300)
player:draw()
end
----------------------------------------
-- INPUT
----------------------------------------
function love.keypressed ( key )
if ( key == "right" ) then
player.moving = true
player.direction = right
elseif ( key == "left" ) then
player.moving = true
player.direction = left
elseif ( key == "space" ) then
player.flying = true
player.actor:switch('flap')
player.actor:getAnimation():restart()
end
end
function love.keyreleased (key)
if ( key == "right" and player.direction == right ) then
if ( love.keyboard.isDown("left") ) then
player.direction = left
else
player.moving = false
end
elseif ( key == "left" and player.direction == left ) then
if ( love.keyboard.isDown("right") ) then
player.direction = right
else
player.moving = false
end
end
end
----------------------------------------
-- FLIERS
----------------------------------------
-- birds and bats both fly. fliers.
Flier = class('Flier')
function Flier:initialize ( x, y, actor )
self.x = x
self.y = y
self.y_vel = 0
self.x_vel = 0
self.moving = false
self.flying = false
self.actor = actor
end
-- generic flier update: physics + changing position
function Flier:update ( dt )
self:physics( dt )
end
function Flier:draw ( )
if ( self.direction == right ) then
self.actor:flipX(true)
elseif (self.direction == left) then
self.actor:flipX(false)
end
self.actor:draw( self.x, self.y )
end
----------------------------------------
-- "PHYSICS"
----------------------------------------
-- "physics" being used verryyyyy lightly here
-- does basics physics work (determines velocity) for a flier
function Flier:physics ( dt )
self:physics_x( dt )
self:physics_y( dt )
end
function Flier:physics_x ( dt )
max_vel = 300
min_vel = -300
turn = 300
-- holding arrow-key
if ( self.moving ) then
if ( self.x_vel < max_vel and self.direction == right ) then
self.x_vel = self.x_vel + (max_vel / turn)
elseif ( self.x_vel > min_vel and self.direction == left ) then
self.x_vel = self.x_vel - (max_vel / turn)
end
else
if ( self.x_vel > 0 ) then
self.x_vel = self.x_vel - (max_vel / (turn * 3))
elseif ( self.x_vel < 0 ) then
self.x_vel = self.x_vel + (max_vel / (turn * 3))
end
end
self.x = self.x + self.x_vel * dt
end
function Flier:physics_y ( dt )
gravity = .85
floor = 500
-- wing-flap
if ( self.flying ) then
self.y_vel = -175
self.flying = false
end
-- gravity
if ( self.y < floor ) then
self.y_vel = self.y_vel + gravity
end
-- if on ground; stop gravity
if ( self.y > floor ) then
self.y = floor
self.y_vel = 0
end
self.y = self.y + self.y_vel * dt
end
Bat = class('Bat', Flier)
function Bat:initialize()
batSheet = love.graphics.newImage("art/sprites/bat.png")
batFlapAnim = animx.newAnimation{
@ -14,7 +166,7 @@ function love.load ()
tileWidth = 32,
frames = { 2, 3, 4, 5 }
}:onAnimOver( function()
bat.actor:switch('idle')
player.actor:switch('idle')
end )
batIdleAnim = animx.newAnimation {
@ -28,144 +180,5 @@ function love.load ()
['flap'] = batFlapAnim
}:switch('idle')
-- batActor:switch('idle')
bat = { x = 50, y = 200,
y_vel = 0, x_vel = 0,
moving = false,
flying = false,
direction = left,
actor = batActor
}
-- for compliance with Statute 43.5 (2019); all birds must report births to local Officials
birdRegistry = {}
end
----------------------------------------
-- UPDATE
----------------------------------------
function love.update ( dt )
-- bat.x = bat.x + 100 * dt
flier_update( bat, dt )
animx.update(dt)
end
----------------------------------------
-- DRAW
----------------------------------------
function love.draw ()
love.graphics.print('Hello World!', 400, 300)
flier_draw( bat )
end
----------------------------------------
-- INPUT
----------------------------------------
function love.keypressed ( key )
if ( key == "right" ) then
bat.moving = true
bat.direction = right
elseif ( key == "left" ) then
bat.moving = true
bat.direction = left
elseif ( key == "space" ) then
bat.flying = true
bat.actor:switch('flap')
bat.actor:getAnimation():restart()
end
end
function love.keyreleased (key)
if ( key == "right" and bat.direction == right ) then
if ( love.keyboard.isDown("left") ) then
bat.direction = left
else
bat.moving = false
end
elseif ( key == "left" and bat.direction == left ) then
if ( love.keyboard.isDown("right") ) then
bat.direction = right
else
bat.moving = false
end
end
end
----------------------------------------
-- FLIERS
----------------------------------------
-- generic flier update: physics + changing position
function flier_update ( flier, dt )
flier_physics( flier, dt )
end
function flier_draw ( flier )
if ( flier.direction == right ) then
flier.actor:flipX(true)
elseif (flier.direction == left) then
flier.actor:flipX(false)
end
flier.actor:draw( flier.x, flier.y )
end
----------------------------------------
-- "PHYSICS"
----------------------------------------
-- "physics" being used verryyyyy lightly here
-- does basics physics work (determines velocity) for a flier
function flier_physics ( flier, dt )
flier_physics_x( flier, dt )
flier_physics_y( flier, dt )
end
function flier_physics_x ( flier, dt )
max_vel = 300
min_vel = -300
floor = 500
turn = 300
if ( flier.moving ) then
if ( flier.x_vel < max_vel and flier.direction == right ) then
flier.x_vel = flier.x_vel + (max_vel / turn)
elseif ( flier.x_vel > min_vel and flier.direction == left ) then
flier.x_vel = flier.x_vel - (max_vel / turn)
end
else
if ( flier.x_vel > 0 ) then
flier.x_vel = flier.x_vel - (max_vel / (turn * 3))
elseif ( flier.x_vel < 0 ) then
flier.x_vel = flier.x_vel + (max_vel / (turn * 3))
end
end
flier.x = flier.x + flier.x_vel * dt
end
function flier_physics_y ( flier, dt )
gravity = .75
floor = 500
-- wing-flap
if ( flier.flying ) then
flier.y_vel = -200
flier.flying = false
end
-- gravity
if ( flier.y < floor ) then
flier.y_vel = flier.y_vel + gravity
end
-- if on ground; stop gravity
if ( flier.y > floor ) then
flier.y = floor
flier.y_vel = 0
end
flier.y = flier.y + flier.y_vel * dt
Flier.initialize( self, 50, 100, batActor )
end