Need Critique on Tutorial Series on Making Arkanoid-type Game.
Need Critique on Tutorial Series on Making Arkanoid-type Game.
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.
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.
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
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:
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!
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},
}
But it's a good effort altogether so keep it up, I'm sure a lot of people will find the tutorial helpful!
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
Well, the tutorial is not about Arkanoid in particular, but more about a typical game code structure.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.
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.
That's the valid point, thanks.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:
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.
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
I don't recommend using classes unless you want to have inheritance.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.
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
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
Code: Select all
ClassMT = {__index=Class}
function Class:new( o )
o = o or {}
setmetatable(o, ClassMT)
.....
return o
end
Code: Select all
ClassMT = {__index=Class}
function Class:new( args )
local o = {}
setmetatable(o, ClassMT)
.....
return o
end
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
Yep, it's up to you really although parsing text files is quite simple using Lua's pattern matching: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.
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.
- zorg
- Party member
- Posts: 3441
- Joined: Thu Dec 13, 2012 2:55 pm
- Location: Absurdistan, Hungary
- Contact:
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
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: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.
In the other thread, i remember i did suggest using Bump instead of HC.noway wrote:HC is indeed overkill.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.
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.
Also, why would a game like arkanoid even need love.physics/box2D? Legit curious.
Me and my stuff True 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.
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
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.
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
Yep, that's an option too although it may not be very well suited for a beginner tutorial.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)
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
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
so each "entity" is just an ID or a handle that is shared between different systems.
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
`setmetatable(o, self)` sets Class as the metatable for an each new class object.For example in your tutorial it's:
Code: Select allI believe "self.__index = self" doesn't do anything in this case.Code: Select all
function Class:new( o ) o = o or {} setmetatable(o, self) self.__index = self ..... return o end
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:
`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:
(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.)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
Let us assume we have a base class like Account:
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
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
Up to now, SpecialAccount is just an instance of Account. The nice thing happens now:Code: Select all
SpecialAccount = Account:new()
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 evaluateCode: Select all
s = SpecialAccount:new{limit=1000.00}
Last edited by noway on Sun Dec 18, 2016 10:29 am, edited 1 time in total.
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
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.
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.
Re: Need Critique on Tutorial Series on Making Arkanoid-type Game.
Yeah, this can cause nasty bugs.Also, I typically recommend creating new instances like so (this avoids bugs caused by Class.new vs Class:new):
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.
I have been thinking to use an external class library instead of 'raw Lua' approach.My suggestion would be to avoid using classes altogether.
Just use a module and return "handles" to your "instances":
This approach is simpler and faster than classes and doesn't involve metatables.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
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...
Who is online
Users browsing this forum: No registered users and 27 guests