Need Critique on Tutorial Series on Making Arkanoid-type Game.

Show off your games, demos and other (playable) creations.
User avatar
zorg
Party member
Posts: 3444
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by zorg »

noway wrote:
For example in your tutorial it's:

Code: Select all

Code: Select all

function Class:new( o )
   o = o or {}
   setmetatable(o, self)
   self.__index = self
   .....
   return o
end
I believe "self.__index = self" doesn't do anything in this case.
Now the code above will probably work... well, kinda...
but if you tried to implement any sort of inheritance then you'll be left scratching your head.
You probably want something like:
`setmetatable(o, self)` sets Class as the metatable for an each new class object.
`self.__index = self` sets `__index` metamethod in the Class to point to Class itself.
To have inheritance you just type `SubClass = Class:new()` and then proceed to define SubClass methods.
You don't even need to define the new constructor for the SubClass.

The code I use is from Programming in Lua.
Below are relevant excerpts (full text: Classes, Inheritance; also found in 3ed, p. 165 ):
Let us go back to our example of a bank account. To create other accounts with behavior similar to Account, we arrange for these new objects to inherit their operations from Account, using the __index metamethod. Note a small optimization, that we do not need to create an extra table to be the metatable of the account objects; we can use the Account table itself for that purpose:

Code: Select all

    function Account:new (o)
      o = o or {}   -- create object if user does not provide one
      setmetatable(o, self)
      self.__index = self
      return o
    end
(When we call Account:new, self is equal to Account; so we could have used Account directly, instead of self. However, the use of self will fit nicely when we introduce class inheritance, in the next section.)
This is my take on this: viewtopic.php?f=14&t=83241&p=207041#p207041

The constructor being part of the class (and the class being called "self" in the constructor), redefining the __index metatable in each constructor call, and doing that AFTER setting the metatable for the instances... it just seems wrong to me.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by airstruck »

You could also skip the metatables and do OOP something like this:

Code: Select all

local function say (self, phrase)
    print(self.name .. " says " .. phrase)
end

local function Guy (name)
    return {
        name = name,
        say = say,
    }
end

local bob = Guy("Bob")
bob:say("hi")
noway
Prole
Posts: 43
Joined: Mon Mar 21, 2011 7:58 am

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by noway »

In the other thread, i remember i did suggest using Bump instead of HC.
Yeah. I remember, I didn't like the fact that Bump doesn't support non-rectangular shapes.
But you are right after all: Bump is more suitable in this case.
HC should be replaced either with Bump or manually written collision detection.
Also, why would a game like arkanoid even need love.physics/box2D? Legit curious.
I mentioned love.physics in the different context, but speaking of Arkanoid, I think, semi-realistic physics can add some variety to Arkanoid-type game: curved shapes; bricks that break only on certain threshold impact energy; etc... love.physics can be used to build a proof-of-concept.
noway
Prole
Posts: 43
Joined: Mon Mar 21, 2011 7:58 am

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by noway »

I like the idea of introducing complex code concepts (collision handling, OOP) through a simple game. I kind of agree, however, that if the added complexity is not needed, introducing it without a big warning in bold letters, that for such a small project these are overkill but you're doing it as an example of use, may confuse readers.
Agreed.

I have started to maintain a Todo list with all the suggestions.
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by ivan »

I've seen similar methods to Airstruck's and they work fine but there is a caveat.
First, it's slower than metatables and second -
you have to be super careful with the function that returns new instances.
Any arguments and local variables will remain in memory for the lifetime of the object.
For example, if your classes are declared like so:

Code: Select all

local function newVector(x, y)
  -- x, y are now available for the lifetime of the object
  local o = {}
  o.method = function()
    ....
  end
  return o
end
This is very useful in certain cases and people use this approach to implement "private" class members.
But then the o.method becomes a "new closure" which means extra work for the GC.

Code: Select all

local function Guy (name)
    return {
        name = name,
        say = say,
    }
end
This is a cute example that avoids unique closures, it just doesn't look very clear IMO.
Heck, you might as well take advantage of the closures anyways
if you decide to use this approach.
Either way, OO programming in Lua is good if you need inheritance.
99% of the time you can get by without "classes".
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by airstruck »

ivan wrote:it's slower than metatables
Are you sure? It looks to me like under PUC Lua, it takes slightly longer to instantiate objects this way, but method resolution is faster. As long as you're calling methods more often than you're instantiating objects, it should be faster there. Under LuaJit, both approaches seem to be basically identical in terms of speed.
Any arguments and local variables will remain in memory for the lifetime of the object.
I'm not sure that's necessarily true either. If there's no closure, there should be no reason to keep those locals around in memory. I haven't checked whether they're kept around or not, but I suspect they're not if there's no closure to keep them around for.
Either way, OO programming in Lua is good if you need inheritance.
I tend to think of polymorphism as the main advantage of OO. Inheritance can be nice sometimes but isn't essential for OO to be useful.
User avatar
Positive07
Party member
Posts: 1014
Joined: Sun Aug 12, 2012 4:34 pm
Location: Argentina

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by Positive07 »

They are kept as upvalues, even if not used, I don't think Lua is smart enough to tell which upvalues are not needed... even more so if you are using stuff like debug.getupvalue and friends, so yeah clojures use more memory this way.

They also use more memory because they create new functions each time a new object is created, complex classes may allocate many functions per object...

Some clojures may not actually need to be clojures (if they don't change behaviour depending on the object), using external functions and simple assignment can solve the problem too.

Alternatively you can combine all three methods:

Code: Select all

function hello (self)
   print("Hello "..self.name.."!")
end

meta = {
   __index = {
      meta = function (self)
         print("Hey "..self.name..", this is a actually a shared function")
      end
   }
   __call = hello
}

function new (name, secret)
   return setmetatable({
      name = name,
      hello = hello, --External function, simple assignment
      secret = function (self) --Clojure
         print("Hey "..self.name..", I know your secret is: "..secret)
      end,
      other = function () --Clojure that can be called with dot notation
         print("I still know your name is: "..name)
      end
   }, meta) --Metatable
end
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by airstruck »

Positive07 wrote:They are kept as upvalues, even if not used ... so yeah clojures use more memory this way.
But there were no closures in the code I posted. What I meant was there should be no reason to retain those locals in memory since there are no closures for them to be upvalues of. Anyway it shouldn't be hard to test, I'll write something up.

Edit: tests seem to confirm that the locals don't stick around in memory if there's no closure. Not only that, apparently the presence of a closure isn't enough to keep them from being collected either; they actually have to be referenced from within the closed-over function. As far as I can tell, the claim that those locals will hang around in memory for the lifetime of the object is false.
User avatar
Positive07
Party member
Posts: 1014
Joined: Sun Aug 12, 2012 4:34 pm
Location: Argentina

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by Positive07 »

I see, my bad... Then I misuderstood. But each methods has it's pro and cons I would argue that:
  • The best is the simple assignment one (since they don't create new functions and don't need complex indexing through metatables). The method airstruck described is what I call simple assignment. You assign the function as a table value.
  • Clojures are second (since they can have private values, and indexing them is as fast as the assignment one). Clojures are new functions created each time a new element is created, they are assigned as a table value
  • I would only recommend to use a metatable either if you already need one (say if you need the __call or __tostring metamethods) or if you have a huge number of functions that you don't wanna assign each time you create an object. And well metatable is the common OOP method in Lua, they are methods of a table that is set as the __index metamethod of the object
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.

Post by ivan »

Each approach has its strengths and of course there are tradeoffs.
Positive07 wrote:The best is the simple assignment one
That would be the fastest way to evoke functions, but clunky and slower to allocate (looks a little bit like mixins). In short, have to manually move data around when you don't use metatables.
Positive07 wrote:Clojures are second
Good for long-lived objects, but a little bit of extra work for the GC. Of course you have the benefit of access to the before-mentioned upvalues.
Positive07 wrote:I would only recommend to use a metatable either if you already need one (say if you need the __call or __tostring metamethods) or if you have a huge number of functions that you don't wanna assign each time you create an object.
Metatables move the least amount of memory during allocation.
There may be a very negligible overhead when evoking functions, but it makes the code simpler and shorter so it's worth it.
Post Reply

Who is online

Users browsing this forum: No registered users and 67 guests