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

Show off your games, demos and other (playable) creations.
Post Reply
noway
Prole
Posts: 43
Joined: Mon Mar 21, 2011 7:58 am

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

Post by noway »

Hey!

I have been writing a tutorial series on making a full-featured Arkanoid-type game.
It is intended for people who have basic programming experience, but have trouble structuring their code for bigger-than-hello-world projects.
I have posted an announcement earlier.

The code in the tutorial is ready, but the text is still in a draft stage.
Recently I have rewritten the first six parts to more-or-less finished form:
1. The Ball, The Brick, The Platform
2. Splitting Code into Several Files (Lua Modules)
3. Classes
4. Other Bricks and The Walls (Container Classes)
5. Detecting Collisions
6. Resolving Collisions

I would like to ask critique on them: what is not clear, what can be done better, any mistakes (I'm sure, there are a lot of them), etc.
Any feedback is appreciated.
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 »

Hello there, I like idea behind this tutorial.
A while ago, we did a 1K Breakout challenge on these forums where the goal was to make an Arkaniod clone in under 1 Kb of code and a few people did really well.

I think your code is OK, to be honest it seems like the use of classes in your case is not really an advantage.

HC is probably overkill for this tutorial - you want to show people how to make Arkaniod right, you don't want to teach them how HC works. The "ball" in this case could be treated as an axis-aligned rectangle which will make all of the collision code significantly shorter and easier to understand.

The section on "Loading Level From External File" could be a separate tutorial altogether. Generally I don't recommend the approach you used, because it contains both code and data. Ideally you want your level format to be data only, for example:

Code: Select all

return {
   {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
   {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
   {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
   {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
   {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
   {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
   {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
   {3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
   {2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
   {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
}
Plain text files are really simple and can be used instead of nested tables, especially when you are dealing with tile-based games.

But it's a good effort altogether so keep it up, I'm sure a lot of people will find the tutorial helpful!
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 think your code is OK, to be honest it seems like the use of classes in your case is not really an advantage.

HC is probably overkill for this tutorial - you want to show people how to make Arkaniod right, you don't want to teach them how HC works. The "ball" in this case could be treated as an axis-aligned rectangle which will make all of the collision code significantly shorter and easier to understand.
Well, the tutorial is not about Arkanoid in particular, but more about a typical game code structure.
Arkanoid is just an example.

IMO, as long as I want to treat each brick as a separate entity with it's own behaviors and properties, there aren't much alternatives to classes. Classes allow to implement such approach in the cleanest way. The only problem with classes, is that they are not available in Lua by default. If I were writing similar thing in Python, I wouldn't have any seconds thoughts on using them. In Lua it is necessary to use either an external library or to deal with metatables and metamethods; both are perceived as unnecessary complication.

HC is indeed overkill.
On the other hand, it is conceptually simple and allows to demonstrate how a typical collision detection system can be incorporated into the code. I remember, it took me a while to come up with a good way to use `HC` and `love.physics`.
Anyway, I don't want to change it right now; but I'll consider to switch to manually-written collision detection in the future.
Generally I don't recommend the approach you used, because it contains both code and data. Ideally you want your level format to be data only, for example:
That's the valid point, thanks.
I'll make appropriate changes.
As for the plain text vs nested tables, by using nested tables I avoid the need to parse the plain text.
I believe, that's a reasonable trade-off.
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 »

noway wrote:IMO, as long as I want to treat each brick as a separate entity with it's own behaviors and properties, there aren't much alternatives to classes. Classes allow to implement such approach in the cleanest way. The only problem with classes, is that they are not available in Lua by default. If I were writing similar thing in Python, I wouldn't have any seconds thoughts on using them. In Lua it is necessary to use either an external library or to deal with metatables and metamethods; both are perceived as unnecessary complication.
I don't recommend using classes unless you want to have inheritance.
but, yes the use of classes can work in this case but it needs more work.
Note that "classes" in Lua can be implemented in different ways.
Metatabes are the easiest approach, but it's tricky.
For example in your tutorial it's:

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:

Code: Select all

function Class:new( o )
   o = o or {}
   setmetatable(o, {__index=Class})
   .....
   return o
end
Since the metatable can be reused, most of the time it's written as:

Code: Select all

ClassMT = {__index=Class}
function Class:new( o )
   o = o or {}
   setmetatable(o, ClassMT)
   .....
   return o
end
Also, I typically recommend creating new instances like so (this avoids bugs caused by Class.new vs Class:new):

Code: Select all

ClassMT = {__index=Class}
function Class:new( args )
   local o = {}
   setmetatable(o, ClassMT)
   .....
   return o
end
I like to plug my own little OO library - sure there are more sophisticated libs out there,
but if you're just looking for clarity on how to use metatables than it might be worth taking a look:
https://github.com/2dengine/oo.lua
As for the plain text vs nested tables, by using nested tables I avoid the need to parse the plain text.
I believe, that's a reasonable trade-off.
Yep, it's up to you really although parsing text files is quite simple using Lua's pattern matching:

Code: Select all

  map = {}
  x, y = 1, 1
  -- for each line
  for line in string.gmatch(str, "(.-)\n") do
    -- for each character
    map[y] = {}
    for c in string.gmatch(line, ".") do
      map[y][x] = c
      x = x + 1
    end
    x, y = 1, y + 1
  end
Last edited by ivan on Wed Dec 15, 2021 11:35 am, edited 1 time in total.
User avatar
zorg
Party member
Posts: 3436
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:IMO, as long as I want to treat each brick as a separate entity with it's own behaviors and properties, there aren't much alternatives to classes. Classes allow to implement such approach in the cleanest way. The only problem with classes, is that they are not available in Lua by default. If I were writing similar thing in Python, I wouldn't have any seconds thoughts on using them. In Lua it is necessary to use either an external library or to deal with metatables and metamethods; both are perceived as unnecessary complication.
You said the magic word yourself, entity. An ECS (entity-component-system) approach would probably work fine instead of a class/oo-based one too, though it might be quite a bit weird at first. (ECS-es aren't available either as built-in functionality either though)
noway wrote:
HC is probably overkill for this tutorial - you want to show people how to make Arkaniod right, you don't want to teach them how HC works. The "ball" in this case could be treated as an axis-aligned rectangle which will make all of the collision code significantly shorter and easier to understand.
HC is indeed overkill.
On the other hand, it is conceptually simple and allows to demonstrate how a typical collision detection system can be incorporated into the code. I remember, it took me a while to come up with a good way to use `HC` and `love.physics`.
Anyway, I don't want to change it right now; but I'll consider to switch to manually-written collision detection in the future.
In the other thread, i remember i did suggest using Bump instead of HC.
Also, why would a game like arkanoid even need love.physics/box2D? Legit curious.
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
pgimeno
Party member
Posts: 3544
Joined: Sun Oct 18, 2015 2:58 pm

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

Post by pgimeno »

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.
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 »

zorg wrote:You said the magic word yourself, entity. An ECS (entity-component-system) approach would probably work fine instead of a class/oo-based one too, though it might be quite a bit weird at first. (ECS-es aren't available either as built-in functionality either though)
Yep, that's an option too although it may not be very well suited for a beginner tutorial.
My suggestion would be to avoid using classes altogether.
Just use a module and return "handles" to your "instances":

Code: Select all

local bricks = {}
bricks.list = {}

function bricks.newBrick(args)
   local o = {}
   .....
   table.insert(bricks.list, o)
   return o
end

function bricks.update(dt)
  for i, o in ipairs(bricks.list) do
    .....
  end
end

.....

return bricks
This approach is simpler and faster than classes and doesn't involve metatables.
To ensure encapsulation, it's important to keep all of the code related to bricks in this one module.
One bad thing is that when your handles are tables then people can access that data outside of the module breaking the encapsulation.
But when your handles are just IDs then you have a full fledged ECS:

Code: Select all

function bricks.newBrick(id, args)
  local o = {}
  asset(bricks[id] == nil, 'id already taken')
  bricks.list[id] = o
end
Another good thing about IDs is that then can be shared between modules,
so each "entity" is just an ID or a handle that is shared between different systems.
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 »

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.)
Let us assume we have a base class like Account:

Code: Select all

    Account = {balance = 0}
    
    function Account:new (o)
      o = o or {}
      setmetatable(o, self)
      self.__index = self
      return o
    end
    
    function Account:deposit (v)
      self.balance = self.balance + v
    end
    
    function Account:withdraw (v)
      if v > self.balance then error"insufficient funds" end
      self.balance = self.balance - v
    end
From that class, we want to derive a subclass SpecialAccount, which allows the customer to withdraw more than his balance. We start with an empty class that simply inherits all its operations from its base class:

Code: Select all

    SpecialAccount = Account:new()
Up to now, SpecialAccount is just an instance of Account. The nice thing happens now:

Code: Select all

    s = SpecialAccount:new{limit=1000.00}
SpecialAccount inherits new from Account like any other method. This time, however, when new executes, the self parameter will refer to SpecialAccount. Therefore, the metatable of s will be SpecialAccount, whose value at index __index is also SpecialAccount. So, s inherits from SpecialAccount, which inherits from Account. When we evaluate
Last edited by noway on Sun Dec 18, 2016 10:29 am, edited 1 time in total.
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 »

Good catch, there, I must have overlooked that technique in the Lua manual.
Either way, I still believe that the former approach is clearer, this one had me fooled at least.
Again, there is no 'standard' with OO in Lua so it's partly a matter of habit I guess. :)
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 »

Also, I typically recommend creating new instances like so (this avoids bugs caused by Class.new vs Class:new):
Yeah, this can cause nasty bugs.
But your approach won't work with the class definition I use.
I like to plug my own little OO library - sure there are more sophisticated libs out there,
but if you're just looking for clarity on how to use metatables than it might be worth taking a look:
https://bitbucket.org/itraykov/oo.lua/src/
Either way, I still believe that the former approach is clearer, this one had me fooled at least.
Again, there is no 'standard' with OO in Lua so it's partly a matter of habit I guess. :)
My suggestion would be to avoid using classes altogether.
Just use a module and return "handles" to your "instances":

Code: Select all

local bricks = {}
bricks.list = {}

function bricks.newBrick(args)
   local o = {}
   .....
   table.insert(bricks.list, o)
   return o
end

function bricks.update(dt)
  for i, o in ipairs(bricks.list) do
    .....
  end
end

.....

return bricks
This approach is simpler and faster than classes and doesn't involve metatables.
I have been thinking to use an external class library instead of 'raw Lua' approach.
Now, seeing that literally everyone has his own preferable way to define classes, I am totally convinced.
The only problem is to choose a class library :)
The alternative approach you suggested -- to avoid OOP altogether -- also has merit.
I can't decide right now which one to choose.
I have been thinking to split the tutorial in two major parts: "Part I: Building Prototype", "Part II: Adding Details".
Probably, 'Part I' should be done without OOP and collision detection libraries, but they should appear in 'Part II' as advanced concepts or something like that...
Post Reply

Who is online

Users browsing this forum: No registered users and 45 guests