middleclass & extras: middleclass 3.0 is out!

Showcase your libraries, tools and other projects that help your fellow love users.
User avatar
Roland_Yonaba
Inner party member
Posts: 1563
Joined: Tue Jun 21, 2011 6:08 pm
Location: Ouagadougou (Burkina Faso)
Contact:

Re: middleclass & extras: middleclass 2.0 is out!

Post by Roland_Yonaba »

TechnoCat wrote:I'm guessing because with invoke you can pass it a variable string that gets invoked.
Sorry, I may not have clearly stated what my point was.
For instance, when I create a class, I do know what are its properties and methods, because i am the one who define them.
It is true that whith Lua tables, these two results in the same way (

Code: Select all

-- assuming class.f = function(class,...) bla bla end
class:f(...) 
   --same as
class['f'](class,...)
That's the basic concept Invoker mixin relies on, I guess.
But, to me, it don't know why I would rather write class:invoke('f',...) instead of class:f(...) which is simpler.

I am not saying Invoker is pointless. Far from that.
I am just asking if there are cases where Invoker is a nice solution.
User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: middleclass & extras: middleclass 2.0 is out!

Post by Robin »

It can replace:

Code: Select all

if func == 'f' then
    class:f(...)
elseif func == 'g' then
    class:g(...)
elseif func == 'h' then
    class:h(...)
end
by

Code: Select all

class:invoke(func, ...)
although I guess
class[func](...)
would work just as well. Unless invoke does something extra?
Help us help you: attach a .love.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: middleclass & extras: middleclass 2.0 is out!

Post by kikito »

The reason Invoker is there is to avoid one extra if.

I've found myself often wanting to be able to parse a list of (potentially heterogeneous) collection of instances, and wanting to call a method on them. "Draw all Enemies", "update all Entities", "pause all Entities not related with the Pause menu".

Sometimes I knew there was a common method name that I could call ("update", or "draw"). Other times, I wanted to use a function - probably an anonymous function. For example, to pause all the entities not related with a menu, I might want to do this:

Code: Select all

function(instance)
  if not relatedWithMenu(instance) then
    instance:pause()
  end
end
I found out that I was doing "if type(method) == 'string' then method = instance[method] end" a bit too often, so I created the Invoker mixin to put it there.
Roland_Yonaba wrote:For instance, when I create a class, I do know what are its properties and methods, because i am the one who define them.
The case is simple when you phrase it like that.

Invoke is to be used with classes that you have *not* built. At least not in the context of what you are writing. This is useful when you are creating a library, and you don't really know what classes - or methods - it will be used with

The most obvious example (and the reason why I created the Invoker mixin in the first place) is the Apply mixin.

This mixin basically "insterts itself" between "new" and "initialize", and stores references to the newly created instances in a private _instances variable. Then you can do YourClass:apply(methodNameOrFunction, param1, param2 ...) and that will parse _instances applying the method to the lib. And you can pass a method name or a function, and it will work.

I hope this clarifies it a bit.
When I write def I mean function.
User avatar
Roland_Yonaba
Inner party member
Posts: 1563
Joined: Tue Jun 21, 2011 6:08 pm
Location: Ouagadougou (Burkina Faso)
Contact:

Re: middleclass & extras: middleclass 2.0 is out!

Post by Roland_Yonaba »

A lot, thanks! :ultrahappy:
User avatar
jasonisop
Citizen
Posts: 93
Joined: Thu Jun 21, 2012 1:35 am

Issues with a class

Post by jasonisop »

Sorry double post
Last edited by jasonisop on Fri Jun 22, 2012 1:15 pm, edited 2 times in total.
User avatar
jasonisop
Citizen
Posts: 93
Joined: Thu Jun 21, 2012 1:35 am

Having an issue with a class ive made

Post by jasonisop »

I posted on this last night here but the post didnt show.

So my issue is this, I have an Enemy class that runs a timed function to look and see if a player is next to it. If the player is next to the Enemy and the Enemy is facing the player it deals it a point of damage. I create two instances of the Enemy class and place them on my map, but only one of the Enemies seems to be running the timed function and when it it damages the player it deals 2 damage instead of the one.

I am new to lua so im sure my code is not quite right and might be a bit messy and redundant in places.

here is the Enemy class. And a .love file for the whole project.

Code: Select all

-- Our Enemy class
require ('luascripts/middleclass')

 Enemy = class('Enemy')

function temps()
	Enemy:checkForPlayer()
end

 function Enemy:initialize(enemyType,enemyImage,enemyWidth,enemyHeight,enemyFacing,enemySpeed,enemyTileX,enemyTileY)
	--public vars
	self.cron = require 'luascripts/cron'
	
	self.type = enemyType -- can be used later for things like insect,human ect...
	self.image = love.graphics.newImage("images/".. enemyImage)
	self.imageName = enemyImage
	self.width = enemyWidth 
	self.height = enemyHeight
	self.x = 0
	self.y = 0
	self.facing = enemyFacing
	self.animating = false
	self.speed = enemySpeed or 150
	self.tileX = enemyTileX or 0
	self.tileY = enemyTileY or 0

	self.noWalk = false
	Enemy:setTileX(self.tileX)
	Enemy:setTileY(self.tileY)
	Enemy:setFacing(self.facing)
	
	self.id = self.cron.every(1, temps)
	--private vars
	self._anim8 = require ('luascripts/anim8')
	self._g =  self._anim8.newGrid(self.width, self.height, self.image:getWidth(), self.image:getHeight())
	self._animation = self._anim8.newAnimation('loop', self._g('1-3,1'), 0.1)
end

--- getters and setters ---
 function Enemy:setEnemyType(paramType)
	self.type = paramType
end
 function Enemy:getEnemyType()
	return self.type
end

 function Enemy:setImage(paramImage)
	self.image = love.graphics.newImage("images/".. paramImage)
end
 function Enemy:getImage()
	return self.image	
end

 function Enemy:getnoWalk()
	return self.noWalk
end

 function Enemy:setFacing(paramFacing)
	self.facing = paramFacing
end
 function Enemy:getFacing()
	return self.facing
end

 function Enemy:setSpeed(paramSpeed)
	self.speed = paramSpeed
end
 function Enemy:getSpeed()
	return self.speed
end

 function Enemy:setX(paramX)
	self.x = paramX
end
 function Enemy:getX()
	return self.x	
end

 function Enemy:setY(paramY)
	self.y = paramY
end
 function Enemy:getY()
	return self.y	
end

 function Enemy:setTileX(paramTileX)
	self.tileX = paramTileX
end
 function Enemy:getTileX()
	return self.tileX
end

 function Enemy:setTileY(paramTileY)
	self.tileY = paramTileY
end
 function Enemy:getTileY()
	return self.tileY
end
--- end of getters and setters ---

--this will turn animations on or off
 function Enemy:animate(paramAnimate)
	self.animate = paramAnimate
end

--sets Enemy location on the map  
  function Enemy:Location(Px,Py,paramFacing)
	Enemy:setFacing(paramFacing)
	
	self.tileX = Px
	self.tileY = Py
	--32 is tile size
	self.x = self.tileX * 32
	self.y = self.tileY * 32
	Enemy:setTileX(self.tileX)
	Enemy:setTileY(self.tileY)
	Enemy:setFacing(paramFacing)
end

--moves the enemy to the tile if it can might add a return false so you can error fix
 function Enemy:moveTile(x,y)

	-- Grab the tile
	 tile = global.layer.tileData(self.tileX + x, self.tileY + y)

	-- If the tile doesn't exist or is an obstacle then exit the function
	if tile == nil then return end
	if tile.properties.obstacle then return end
	-- Otherwise change the guy's tile
	self.tileX = self.tileX + x
	self.tileY = self.tileY + y

end

 function Enemy:checkTile(x,y)
	-- Grab the tile
	 tile = global.layer.tileData(tileX+x, tileY+y)
	
	-- If the tile doesn't exist or is an obstacle then exit the function
	if tile == nil then 
	self.noWalk = true 
	return 
	end
	
	if tile.properties.obstacle then 
	self.noWalk = true 
	return 
	end
	
	self.noWalk = false
end

--might want to rename this to setAnimation
 function Enemy:setDirection(parmDirection,paramAnimation)
	
	Enemy:setFacing(parmDirection)
	if paramAnimation =="walk" then
	--	Enemy:setImage(self.imageName)
				
		if 	 	self.facing == "up" then	self._animation = self._anim8.newAnimation('loop', self._g('1-3,1'), 0.1)
		elseif  self.facing  == "left" then self._animation = self._anim8.newAnimation('loop', self._g('1-3,2'), 0.1)
		elseif  self.facing  == "down" then self._animation = self._anim8.newAnimation('loop', self._g('1-3,3'), 0.1)
		elseif  self.facing  == "right" then self._animation = self._anim8.newAnimation('loop', self._g('1-3,4'), 0.1)
		end
	 end
	if paramAnimation =="stand" then
	--	Enemy:setImage(self.imageName)
		if 	 	self.facing == "up" then	self._animation = self._anim8.newAnimation('loop', self._g('1-3,1'), 0.1)
		elseif  self.facing  == "left" then self._animation = self._anim8.newAnimation('loop', self._g('1-3,2'), 0.1)
		elseif  self.facing  == "down" then self._animation = self._anim8.newAnimation('loop', self._g('1-3,3'), 0.1)
		elseif  self.facing  == "right" then self._animation = self._anim8.newAnimation('loop', self._g('1-3,4'), 0.1)
		end
	 end

		
end

 function Enemy:checkForPlayer()
		
	local tempX = 0
	local tempY = 0
	
	if 	 	Enemy:getFacing() == "up" then		tempY = -1
	elseif  Enemy:getFacing()  == "down" then 	tempY = 1
	elseif  Enemy:getFacing()  == "left" then	tempX = -1
	elseif  Enemy:getFacing()  == "right" then 	tempX = 1
	end
				
		if	player.getTileX() ==  	Enemy:getTileX() + tempX and player.getTileY()  ==  Enemy:getTileY()+ tempY  then
		--iif	player.getTileX() ==  	self.tileX + tempX and player.getTileY()  == self.tileY + tempY  then
			global.player_Health = global.player_Health - 1
		else 
			global.player_Health = 10
		end
		
end

 function Enemy:update(dt)
	self.cron.update(dt)
	--updates the animation
	self._animation:update(dt)
end

 function Enemy:draw()
	self._animation:draw(self.image, self.x, self.y )
end

return Enemy
Attachments
Game.love
(2.03 MiB) Downloaded 132 times
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: middleclass & extras: middleclass 2.0 is out!

Post by kikito »

Hi there! I've decided to release middleclass 3.0.

The functionality stays in parity with 2.0, but I have decided to finally hide all those global variables, which is the correct thing to do when you create a lua library.

Old way:

Code: Select all

require 'middleclass'

MyClass = class('MyClass')
assert(subclassOf(Object, MyClass))
assert(instanceOf(Object, MyClass:new()))
New way:

Code: Select all

local class = require 'middleclass'
local Object = class.Object

MyClass = class('MyClass')
assert(MyClass:isSubclassOf(Object))
assert(MyClass:new():isInstanceOf(Object))
Those are the main changes. You can see a list of all the changes in the changelog. I have also written a detailed update guide.

Regards!

EDIT: I just noticed the old unanswered message from jasonisop. I must have received it while I was on holidays last year and I didn't see it. If this is still an issue, jasonisop, let me know.

EDIT2: Also, I haven't tried it, but I suspect that Stateful works with middleclass 3.0 without any modifications.
When I write def I mean function.
User avatar
ejmr
Party member
Posts: 302
Joined: Fri Jun 01, 2012 7:45 am
Location: South Carolina, U.S.A.
Contact:

Re: middleclass & extras: middleclass 3.0 is out!

Post by ejmr »

Thank you for the updates, especially hiding the global variables. That was the only minor thing I didn't like. So that change alone makes this awesome library even better. ^^
ejmr :: Programming and Game-Dev Blog, GitHub
南無妙法蓮華經
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: middleclass & extras: middleclass 3.0 is out!

Post by kikito »

middleclass was my learning project in Lua. When I started it I quite didn't understand what where the best practices. I've got a few of them down:
  • Never create global variables. That is just bad in Lua.
  • Always return a table (it can be a callable table). The reason for returning a table instead of a function is:
  • Include a __VERSION field in the table you return. That way it's very easy to check which version of the lib you are using during runtime. You can add other fields like __DESCRIPTION, __URL and __LICENSE too.
  • Use dot (.) instead of colon (:) in your root object (do myLib.foo(), not myLib:foo())
  • Don't store state in your library if you can avoid it. Instead, provide creators/handlers for the things that have state, and return them to the user. One possible exception to this rule is caching stuff. But if you can, also export the "cache creator" and let the state live in the user's program.
  • Make as little public methods as you can. Check inputs (param types, etc) on those methods only.
  • If you need to split your lib into several files, use the PATH trick to require files inside your lib.
Those are the main ones, but I'm still learning. I'm plan to slowly go over my existing libs and apply these rules to them. But at the same time, I really want to finish bump 2.0. So we'll see.
When I write def I mean function.
User avatar
Nixola
Inner party member
Posts: 1949
Joined: Tue Dec 06, 2011 7:11 pm
Location: Italy

Re: middleclass & extras: middleclass 3.0 is out!

Post by Nixola »

I fear the path trick doesn't work if someone uses lf.load instead of require. There should be no reason for that, but you can never be too sure
lf = love.filesystem
ls = love.sound
la = love.audio
lp = love.physics
lt = love.thread
li = love.image
lg = love.graphics
Post Reply

Who is online

Users browsing this forum: No registered users and 7 guests