What am I doing wrong here?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Wulfie
Prole
Posts: 12
Joined: Mon Apr 11, 2011 6:46 pm

Re: What am I doing wrong here?

Post by Wulfie »

Thanks for the feedback. I have worked on some other games and engines in the past just nothing in lua yet. one that I worked on was VB and everything was stored in SQL, mostly due to the fact that it was an Orpg and the info needed to be called from the server to the clients and back again to store info. I will keep dinking around, so far I'm liking Lua.. loving love and from all of the posts I have seen this seems like a really cool community.
"We do not stop playing because we grow old. We grow old because we stop playing." -- Unknown
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: What am I doing wrong here?

Post by kikito »

Hi Jasoco,

Using any of the existing OOP libraries will help you make what you want more easily.

Allow me to present how you could do that with middlecass. The other libraries are similar, with changes in the syntax.

First, if you are making classes, you don't need to add "type" - you can call your class Enemy instead of "enemyType":

Code: Select all

require 'middleclass.init'
Enemy = class('Enemy')
function Enemy:initialize(x,y)
  self.hp = 1
  self.x = x
  self.y = y
end
function Enemy:update(dt) end
function Enemy:draw() end
function Enemy:delete() end
I've put empty update & draw methods (functions in classes are usually called like that, 'methods'). These ensure that no matter what type of enemy you are working with, if you call "update", you will get no errors.

initialize would be your "onLoad" function, and delete could be your "onDeath". I've taken the liberty of adding a couple parameters to initialize, and using them to set up the enemy's x and y.

You can create your first enemy already, using Enemy:new(x,y)

Code: Select all

local billy = Enemy:new(100,200)
And you can call billy:draw() and billy:update(dt). However, since they are empty functions, this will not do anything.

Now your Blob enemy type can be a subclass of Enemy. This is were the magic begins:

Code: Select all

Blob = class('Blob', Enemy) -- note the Enemy mention here
function Blob:initialize(x,y)
  Enemy.initialize(self, x, y)
  self.hp = 2
end
function Blob:draw()
  -- Assuming that blobImage is loaded somewhere else
  love.graphics.draw(blobImage, self.x, self.y)
end
First, I've changed the initialize method so it changes the initial hp of blobs to 2. But I'm still using the Enemy.initialize method for the "common Enemy creation parts" - in this case, setting up x and y.

Then I have overridden the default Enemy.draw implementation, which did nothing, with a Blob-specific one, which draws each blob on the coordinates it has (self.x, self.y).

You can create a Blob enemy by using Blob:new()

Code: Select all

local timmy = Blob:new(300, 400)
timmy:draw() -- draws it on 300, 400
timmy.x, timmy.y = 100, 200
timmy:draw() -- draws on 100, 200
It is also worth noting that timmy will also have inherited from Enemy the default update() and delete() methods; you could have overriden them if you wanted to.

This is completely extensible. For example, you could create a "FireBlob", as a subclass of Blob, with a different image, hp and update function. Or you could create a totally different Enemy subclass, called "Orc". An then add a "ShamanOrc". And so on.

This is with middleclass alone; with middleclass-extras you can do more stuff - for example automating the addition/deletion of all enemies to an "enemies" table using Apply. But I hope this gives you a taste.
When I write def I mean function.
User avatar
BlackBulletIV
Inner party member
Posts: 1261
Joined: Wed Dec 29, 2010 8:19 pm
Location: Queensland, Australia
Contact:

Re: What am I doing wrong here?

Post by BlackBulletIV »

Wulfie wrote:Thanks for the feedback. I have worked on some other games and engines in the past just nothing in lua yet. one that I worked on was VB and everything was stored in SQL, mostly due to the fact that it was an Orpg and the info needed to be called from the server to the clients and back again to store info. I will keep dinking around, so far I'm liking Lua.. loving love and from all of the posts I have seen this seems like a really cool community.
Oh that's good. By the sounds of things I thought you were another one of those people completely new to game development wanted to make this awesome RPG thing. Well good luck then. :)
User avatar
Wulfie
Prole
Posts: 12
Joined: Mon Apr 11, 2011 6:46 pm

Re: What am I doing wrong here?

Post by Wulfie »

Awesome Kikito, this example definitely helped me out in understanding how middleclass works. :awesome:
"We do not stop playing because we grow old. We grow old because we stop playing." -- Unknown
User avatar
Jasoco
Inner party member
Posts: 3727
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: What am I doing wrong here?

Post by Jasoco »

Yeah! I think I am going to look into that. You make it look so easy.
User avatar
rhezalouis
Party member
Posts: 100
Joined: Mon Dec 07, 2009 10:27 am
Location: Indonesia
Contact:

[Response] Class and Inheritance in Lua

Post by rhezalouis »

Hi Jasoco,
Jasoco wrote:But that's what I'm trying to do. Create an enemy from a set of enemy types. Each type needs to have its own functions for stuff. I don't know how to use metatables. Even after reading the official documentation on Lua.org.
I guess the problem to understand this feature is because there are several terms to address the same thing which create lots of confusion. :crazy:
Jasoco wrote:I want to be able to create enemies from enemy types so that when I say enemy[#enemy+1] = enemyType["blob"] it transfers all the variables from the enemy type into the enemy. And I want each type to have its own onLoad(), update(), draw() and onDeath() functions attached that are different for each one.
Let me try to explain the concept of Prototyping/OOP-Class-Emulation in Lua along with an example; I will use several terms in parallel to show you the equivalency:
Objective: Create an enemy/blob class which would define the parameters and functions for new enemies.
Terms Listing --> Implementation Sample:
  • Prototype == Class--> blobClass == blobPrototype
  • Instance == Child == Descendant ~= Clone --> blob1 == enemy[1]
    Note that the term clone used by Robin refers to instances with same parameters at all time.
    Robin wrote:you will have an army of clones
Code1:
  1. First you create a table that would collect the functions accessible by each blob instance you will be creating:

    Code: Select all

    blobPrototype = {}
    It's simply a table! ^^ It is called prototype, because it is the blueprint for the new blob instances.
  2. Next, you have to define the behaviour of this blueprint that would be copied by its descendant/instance. Here's the reset function:

    Code: Select all

    function blobPrototype:reset(pHealth, pLevel)
      self.HP = pHealth or self.HP or 1
      self.level = pLevel or self.level or 1
      self.age = 0
    end
    A colon is used instead of period to enable the self parameter. This self variable allows each instance to have its own properties because it is dynamically filled, dependent to the caller ( :awesome: Such a useful feature!).E.g.

    Code: Select all

    blobPrototype:reset() --> self = blobPrototype
    blob1:reset() --> self = blob1;
    Compare the previous function with the following static code:

    Code: Select all

    --Do not use this code--
    function blobPrototype.reset(pHealth, pLevel)
      blobPrototype.HP = pHealth or self.HP or 1
      blobPrototype.level = pLevel or self.level or 1
      blobPrototype.age = 0
    end
    The function would always change the same HP, level, and age variable: those in the blobPrototype table.
  3. Similarly, you could define more functions/behaviour for the prototype ^^ :

    Code: Select all

    function blobPrototype:update(dt)
      self.age = self.age + (dt or 0)
    end
    function blobPrototype:draw()
      print("There is a ".. self.age .. "-year(s)-old level" .. self.level .. " blob with " .. self.HP .. " HP")
    end
  4. Next, create a constructor function to create a new instance based on the blobPrototype. The new instance is basically another table that is made aware about its prototype. This relationship between the prototype and its instances is defined in another table. That table called the instance's metatable. Let's see how to assign a table as a metatable for the new instance:

    Code: Select all

    function newBlob(pHealth, pLevel)
      local blobInstance = {}
      setmetatable(blobInstance, {__index = blobPrototype})
      return blobInstance
    end
    Everytime we call this constructor, a new table would be created and 'stored' in the variable blobInstance. If you drop the local modifier, each call would refer to the same global variable that 'store' a single table.
    Reading the next line from the right, the constructor then create a new anonymous table with one member:

    Code: Select all

    --Pseudo Code--
    key = "__index"
    value = blobPrototype --the prototype table
    This anonymous table is then set as the metatable for blobInstance table. The member of the metatable is called metamethod. [Metatable|Metamethod?] [__index?]
    "setmetatable(blobInstance, {__index = blobPrototype})" could be translated into: "When accessing an index that is nil in blobInstance, look for it in blobPrototype." I'll explain more on this at the example. :death:
  5. Usage Example:

    Code: Select all

    blob1 = newBlob(3, 2)
    blob2 = newBlob()
    blob1:update(1)
    blob2:update(1)
    blob1:draw()   --> There is a 1-year(s)-old level2 blob with 3 HP  
    blob2:draw()   --> There is a 1-year(s)-old level1 blob with 1 HP
    blob1 has no function called update, so Lua would redirect it to the __index metamethod entry (i.e. blobPrototype); and thus, calling update function from blobPrototype but the self is set to the original caller i.e.. blob1. So, blob1:draw() is equivalent to:

    Code: Select all

    --Pseudo Code--
    blobPrototype.draw(self = blob1)
If you're still somber, let's increase the code flexibility.
Code2:
  1. We would modify only the constructor to open new features to this prototype:

    Code: Select all

    function blobPrototype:new(pHealth, pLevel)
      local blobInstance = {}
      setmetatable(blobInstance, {__index = self})
      blobInstance:reset(pHealth, pLevel)
      return blobInstance
    end
    The constructor function now is embedded in the prototype, thus, enabling the use of self. Then, change the metamethod __index value to self so that every instance of this prototype could be a prototype as well. This is called Inheritance in OOP.
  2. Usage Example:

    Code: Select all

    enemy = {}
    enemy[1] = blobPrototype:new(3, 2)
    enemy[2] = enemy[1]:new()           --inherit the values of enemy[1]
    
    --update
    for i, ithEnemy in ipairs(enemy) do
      ithEnemy:update(1)
    end
    
    --draw
    for i, ithEnemy in ipairs(enemy) do
      ithEnemy:draw()
    end
    When we call enemy[2].draw(), the code is translated twice:

    Code: Select all

    --Pseudo Code--
    enemy[1].draw(self = enemy[2])
    blobPrototype.draw(self = enemy[2])
Hoo, I'm sorry for such a long post... Do you get it btw? :megagrin:
I think that explains it all. Please CMIIW. And ask again if you're still confused. :ultrahappy:

Regards,
RhezaLouis
Attachments
oopEmulation.zip
Lua Code
(895 Bytes) Downloaded 85 times
Aargh, I am wasting my posts! My citizenshiiiip... :o
User avatar
Jasoco
Inner party member
Posts: 3727
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: What am I doing wrong here?

Post by Jasoco »

It worked! You have changed my coding ways. Thank you!

Seriously, this is going to revolutionize how I create my games from now on. And will probably let me continue working on my Adventure engine that I had stopped working on for a while because I couldn't figure out a good method to use for enemies and their behaviors. Now I can make them all classes like I wanted to and just create new() ones with their own behaviors and everything with a lot less code and confusion. I still have to rewrite some of that engine anyway.
User avatar
nevon
Commander of the Circuloids
Posts: 938
Joined: Thu Feb 14, 2008 8:25 pm
Location: Stockholm, Sweden
Contact:

Re: What am I doing wrong here?

Post by nevon »

Jasoco wrote:Seriously, this is going to revolutionize how I create my games from now on. And will probably let me continue working on my Adventure engine that I had stopped working on for a while because I couldn't figure out a good method to use for enemies and their behaviors. Now I can make them all classes like I wanted to and just create new() ones with their own behaviors and everything with a lot less code and confusion. I still have to rewrite some of that engine anyway.
If you have access and opportunity, I would highly recommend that you attend/audit an object orientation class at some kind of educational institution - preferably one that's language agnostic and deals specifically with object orientation. While you can most certainly learn it on your own, attending a few lectures on the basic theory has been very helpful for me at least.

That said, I find it amazing that you were able to create your adventure game engine without knowing OOP. That must have been hell.
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: What am I doing wrong here?

Post by bartbes »

nevon wrote:I would highly recommend that you attend/audit an object orientation class at some kind of educational institution
Hah, like the class you got? I heard nothing but complaints..
User avatar
nevon
Commander of the Circuloids
Posts: 938
Joined: Thu Feb 14, 2008 8:25 pm
Location: Stockholm, Sweden
Contact:

Re: What am I doing wrong here?

Post by nevon »

bartbes wrote:
nevon wrote:I would highly recommend that you attend/audit an object orientation class at some kind of educational institution
Hah, like the class you got? I heard nothing but complaints..
That's because my teachers were incompetent. :P Also, I already knew how to program, and I already knew about object orientation. But I see how it could be useful to someone who has programming experience, but doesn't know anything about OO (if the teachers don't suck, that is).
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Semrush [Bot] and 10 guests