Page 1 of 2

Pygame Rect in Love?

Posted: Mon Mar 14, 2016 4:28 am
by 59 Byte
So I'm very used to pygame. I've been using it for a while, and I want to switch over to love for many reasons.

In pygame, there is this handy little object class called a Rect that represents a rectangle. The class has variables (ie. Left, right, top, bottom) that change automatically when the other variables are changed. The class also comes with simple collisions for other Rects and points.

I use this Rect class constantly when working with pygame. My games practically revolve around them. Is there something like this Rect class that can be used in love?

Thank you in advance.

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 4:57 am
by XxHAMADEHxX
I'm also coming from Pygame. I just wanted to add the pygame rect from the documentation to help better understand what OP is trying to do.

http://pygame.org/docstest/ref/rect.html

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 5:59 am
by zorg
From what i've seen, it wouldn't be that hard to write a lib that can do the same things that pygame's rect can.

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 6:09 am
by MadByte
Welcome 59 Byte.

I wonder why so many people coming here from PyGame since it doesn't look much inviting to use.
Is it teached in school classes or something like that? :o:

LÖVE itself doesn't contain any predefined classes for you to use, but you can totally create something like this on your own.
The easiest way would be to use an OOP library like Middleclass or Classic.

Here is a small example on how I would start to implement this. (unpack to see the code)
Rect.love

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 6:10 am
by Kingdaro
It's fairly trivial to make your own. Here's a part of the implementation I use:

Code: Select all

Rect = {}
Rect.__index = Rect

function Rect.new(width, height, ...)
  local self = setmetatable({}, Rect)
  self.x, self.y = 0, 0
  self.width = width
  self.height = height
  if ... then self:setPoint(...) end
  return self
end

function Rect:getPoint(nx, ny, ox, oy)
  local x = self.x + self.width * nx + (ox or 0)
  local y = self.y + self.height * ny + (oy or 0)
  return x, y
end

function Rect:setPoint(nx, ny, x, y)
  self.x = x - self.width * nx
  self.y = y - self.height * ny
end
Along with a function for unpacking the rectangle's position and size for convenience, e.g. when using love.graphics.rectangle. This is the whole of it though.

The :getPoint() function returns a position on a rectangle using normalized coordinates, with an optional offset in pixels. :setPoint() sets the position of the rectangle using normalized coordinates as the center.

For the point coordinates, 0 is the left/top of the box, and 1 is the right/bottom on each axis, where 0.5 is the center of each. If you have a box at 0, 0 with a width and height of 100, 200, box:getPoint(0.5, 0.5) == 50, 100. It's a convenient system that takes a lot of math out of the equation.

The constructor optionally accepts any other arguments and passes them to :setPoint().

Example:

Code: Select all

-- create a rect representing the screen
screen = Rect(love.graphics.getDimensions())

-- create a floor, position its bottom middle to the screen's bottom middle, and offset it by 10 pixels up
floor = Rect(800, 50, 0.5, 1, screen:getPoint(0.5, 1, 0, -10))

-- create a player, position its bottom middle to the floor's top middle
player = Rect(80, 80, 0.5, 1, floor:getPoint(0.5, 0))
You can do it pretty much any other way you want, though. It's possible there's something like this lying around somewhere else as well. Tesselode's drawboxes might be of interest to you.

Aaaand I've been ninja'd twice. Lovely. Oh well. ¯\_(ツ)_/¯

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 7:16 am
by 59 Byte
Thanks a lot for for the responses!

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 7:48 am
by zorg
Okay so i was bored and i used Kingdaro's example as a starting point:

Code: Select all

Rect = {}
Rect.__index = Rect

local new(width, height, ...)
  local self = setmetatable({}, Rect)
  -- Dimensions
  self.width = width
  self.height = height
  if ... then
    self:setPoint(...)
  else
    -- Do try to set defaults for the topleft of the rectangle, so getPoint doesn't error.
    self.setPoint(0, 0)
  end
  return self
end

function Rect:copy()
  return new(self.width, self.height, 0, 0, self.x, self.y)
end

function Rect:getPoint(nx, ny, ox, oy)
  local x = self.x + self.width * nx + (ox or 0)
  local y = self.y + self.height * ny + (oy or 0)
  return x, y
end

function Rect:setPoint(nx, ny, x, y)
  self.x = (x or 0) - self.width * nx
  self.y = (y or 0) - self.height * ny
end

-- Do note that these do not create new rectangles since that'd be wasting resources,
-- all of them operate 'in place'.

function Rect:translate(tx, ty) -- move_ip
  self.x = self.x + (tx or 0)
  self.y = self.y + (tx or 0)
end

function Rect:scale(sx,sy,x,y)
  self.width  = self.width  * sx + x
  self.height = self.height * sy + y
end

function Rect:scaleCentered(sx,sy,ox,oy) -- inflate_ip, except gives multipliers as well
  local w,h = self.width  * sx + x, self.height * sy + y
  self.x = self.x + self.width  / 2 - w / 2
  self.y = self.y - self.height / 2 - h / 2
  self.width, self.height = w, h
end

function Rect:clamp(rect) -- clamp_ip
  -- Move self so it's inside rect, unless self is bigger than rect, in case, center only (per-axis).
  if self.width > rect.width then
    self.x = rect.x + rect.width / 2 - self.width / 2
  else
    math.max(math.min(self.x, rect.width), rect.x)
  end
  if self.height > rect.height then
    self.y = rect.y + rect.height / 2 - self.height / 2
  else
    math.max(math.min(self.y, rect.height), rect.y)
  end
end

function Rect:clip(rect) -- clip_ip (doesn't seem to exist)
  -- Test for totally-separatedness
  if self.x > rect.x + rect.width or
     self.x + self.width < rect.x or
     self.y > rect.y + rect.height or
     self.y + self.height < rect.y
  then
    self.x = 0
    self.y = 0
    self.width  = 0
    self.height = 0
  else
    -- Unorthodox method, but should work
    local x = {self.x, rect.x, self.x+self.width, rect.x+rect.width}
    local y = {self.y, rect.y, self.y+self.height, rect.y+rect.height}
    table.sort(x); table.sort(y)
    self.x, self.y, self.width, self.height = x[2], y[2], x[3]-x[2], y[3]-y[2]
end

function Rect:union(...) -- union(all)_ip
  -- take the min of x,y and the max of width,height of all given rects
  local infx, infy, supw, suph = math.huge, math.huge, 0, 0
  for i,v in ipairs(...) do
    infx = math.min(infx, v.x)
    infy = math.min(infy, v.y)
    supw = math.max(supw, v.width)
    suph = math.max(suph, v.height)
  end
  self.x = math.min(self.x, infx)
  self.y = math.min(self.y, infy)
  self.width  = math.max(self.width,  supw)
  self.height = math.max(self.height, suph)
end

function Rect:fit(rect) -- fit_ip (doesn't seem to exist)
  -- Make it so our rectangle fits inside another
  self.x = math.max(self.x, rect.x)
  self.y = math.max(self.y, rect.y)
  self.width  = math.min(self.width,  rect.width)
  self.height = math.min(self.height, rect.height)
end

-- Normalize doesn't exist since we don't allow negative width or heights.
-- (In other words, x and y will always be the top-left coords of a rect.)

function Rect:contains(rect) -- contains
  -- Test whether self is inside rect.
  return self.x >= rect.x and self.y >= rect.y and self.width <= rect.width and self.height <= rect.height
end

function Rect:intersects(rect) -- colliderect
  return (self.x >= rect.x and self.x <= rect.width) or (self.y >= rect.y and self.y <= rect.height)
end

-- Everything else can be implemented with the above two functions (in terms of collision checking)

-----------
return new
Fair warning, this was written in one go, all untested, but should mostly work.

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 8:01 am
by T-Bone
Wasn't one of the big features of Pygame's Rect, that if you modify some variable on it, like bottom, the rect itself would change accordingly? That's totally doable in Lua, but a bit messy (you'd need to overwrite the __index with a custom function, I guess)

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 8:44 am
by zorg
T-Bone wrote:Wasn't one of the big features of Pygame's Rect, that if you modify some variable on it, like bottom, the rect itself would change accordingly? That's totally doable in Lua, but a bit messy (you'd need to overwrite the __index with a custom function, I guess)
Alternatively use functions like setBottom, but yes, checking the name of the key one wants to reach and then modifying the "real" members accordingly is doable as well.

Re: Pygame Rect in Love?

Posted: Mon Mar 14, 2016 11:29 am
by T-Bone
I just realized something. If I change "bottom" to a larger value, should that mean that the rectangle moves down, or that it becomes taller? That's not obvious to me. Maybe functions like "moveToBottom" and "expandToBottom" are better for clarity.