8. Classes in Codea (2)
To make this more interesting, I want to add more balls, and I’ll make them random sizes, colors etc. Then we’ll get them bouncing about, and extend our class(es).
I’ll get the Ball class to do this like so
function Ball:init(x,y,d,c) self.x=x or math.random(0,WIDTH) self.y=y or math.random(0,HEIGHT) self.diameter=d or math.random(50,200) self.colr=c or color(math.random(0,255),math.random(0,255),math.random(0,255)) end
This uses a neat Lua coding trick. When I say
self.x = x or math.random(0,WIDTH)
Then Codea will use any x value I provide, but if it’s nil, it will use the “or” option, a random value, instead. So I don’t have to specify any measurements at all, unless I want to.
Now I can create a whole lot of random balls like this in my main setup function.
function setup() b={} for i=1,15 do b[i]=Ball() end end
And when I run my code, I get something like this
So if you aren’t used to classes, I hope you can see just how simple and clean the Main tab is becoming, as we move all the ball stuff into the Ball class.
Movement
So let’s make the balls move about, and bounce off each other and the walls. We’ll need some physics to help us, and I think the neatest way to do this is to put all of that in a class of its own, which I will call Physics.
So I want to add a line to Ball:init() like so
self.p=Physics(CIRCLE,{self.diameter/2},self.x,self.y)
which passes through the physics type, diameter, and x,y position, and returns a physics object called self.p, that will tell us where to draw our ball.
In Ball:draw() we’ll add these lines to replace the ellipse line
pushMatrix() local x,y,a=self.p:currentPosition() translate(x,y) rotate(a) ellipse(0,0,self.diameter) popMatrix()
First, I’m going to write a function in Physics, called currentPosition, that will give me x,y and angle. Once I know those, then I rotate the screen, move (“translate”) to the x,y position, and draw my circle at that point. (I explained all this a couple of posts ago, if you need to refresh your memory).
So you can see that my Ball class didn’t need much code to handle physics. And it shouldn’t have to, because it should be just about the ball itself.
But I still have to write my physics class. Here it is
Physics = class() --type=CIRCLE --data is a table. For circles it is one value, the radius --(why a table for one point? I was thinking ahead, you'll see later) --x,y is the initial position of the centre --a is the initial angle --g is the gravity value, 0=tabletop, 1=normal (things fall down) --r is the restitution, ie springiness --f is friction --lv is linear velocity --i is any info you want to store to identify this object, eg a name function Physics:init(type,data,x,y,a,g,r,f,lv,i) self.body=physics.body(CIRCLE,data[1]) self.body.x=x self.body.y=y self.body.angle=a or 0 if g then self.body.gravityScale=1 else self.body.gravityScale=0 end self.body.restitution=r or 1 self.body.friction=f or 0.1 if lv==nil then lv=vec2(100+math.random(400),100+math.random(400)) end self.body.linearVelocity=lv self.body.info=i end function Physics:currentPosition() return self.body.x, self.body.y,self.body.angle end
This is fairly straightforward. Once again, I’ve calculated some random values where nothing is provided by the user. The currentPosition function returns the current x,y and angle.
If you’re curious about how it looks when it runs, here it is.
I’m not going to share the full code just yet, because I want to show you one more thing first.
Suppose we now decide we’d like to add some rectangles. It’s surprisingly easy, because we are using classes. We will need a rectangle class, and we’ll need to get Physics to handle rectangles, and finally we’ll need to create some rectangles on the screen in Main.
Here is the Rectangle class, most of the code copied from Ball
Rect = class() function Rect:init(x,y,w,h,c) self.x=x or math.random(0,WIDTH) self.y=y or math.random(0,HEIGHT) self.width=w or math.random(60,150) self.height=h or math.random(30,100) self.colr=c or color(math.random(0,255),math.random(0,255),math.random(0,255)) local data={} table.insert(data,vec2(-self.width/2,-self.height/2)) table.insert(data,vec2(-self.width/2,self.height/2)) table.insert(data,vec2(self.width/2,self.height/2)) table.insert(data,vec2(self.width/2,-self.height/2)) self.p=Physics(POLYGON,data,self.x,self.y) end function Rect:draw() pushStyle() -- store style settings fill(self.colr) --set color pushMatrix() local x,y,a=self.p:currentPosition() translate(x,y) rotate(a) rect(-self.width/2,-self.height/2,self.width,self.height) popMatrix() popStyle() -- put back style settings end
Note how we set width and height rather than diameter, and we pass through a set of four x,y points, or vectors, to Physics. These are the corner points, relative to the centre of the rectangle – so the lower left point has an x value of – 1/2 the width, and y is -1/2 the height, for example.(Now you can see why I used an array to pass the ball diameter, because I want to use the same Physics class for both).
In draw, the only difference is that I draw a rectangle. Note that because we’ve “transformed” the screen so that 0,0 is sitting right over where the centre of the rectangle is to go, our bottom left point is – 1/2 the width, and y is -1/2 the height.
And there is only one change to the physics class. We need to test whether we are getting a circle or rectangle (POLYGON, to Codea). If it is a rectangle, then the “unpack” function will put all our points into the physics object.
if type==CIRCLE then self.body=physics.body(CIRCLE,data[1]) elseif type==POLYGON then self.body=physics.body(POLYGON,unpack(data)) end
Finally, our Main class needs to create and draw some rectangles. This is dead easy.
function setup() b={} for i=1,5 do b[i]=Ball() end r={} for i=1,0 do r[i]=Rect() end CreateWalls() end function draw() background(195, 195, 201, 255) for i=1,#b do b[i]:draw() end for i=1,#r do r[i]:draw() end end
And this is how the whole thing looks….
I suspect you will be surprised how little code there is, in the end.
I’m sorry if I’ve gone a bit fast above, because I am consciously trying to take things slowly. Let me know if the pace is wrong.
Wow! Thanks for creating these tutorials – I am new to Lua and Codea and this information is essential, clear and easy to understand – and just what I needed! The pace is good and I really hope you continue sharing your knowledge this way.
regards, J
Very useful. Thanks again.
I didn’t understand one thing
What does the ‘return’ thing in the physics class, function currentPosition mean?
Functions in Codea can either just “do stuff” for you, or they can give you back information, using the return command.
So if you wrote a function to convert degrees to radians, it might look like this
function degreesToRadians(d)
return d*math.pi/180
end
And you would use it like this
r=degreesToRadians(40)
In the example you quoted, the function returns several items
returns self.body.x, self.body.y,self.body.angle
so you would use it like this
current.x, current.y,current.angle=currentPosition()
ie providing one variable for each item being returned
There are lots of examples in my other posts, because most functions return values like this.
Thanks a lot!!
That’s ok, hope you have lots of fun with Codea. It gets much easier once you understand the basics.
I wrote this code after seeing this tutorial
function setup()
b = {}
for i = 1,10 do
b[i] = Ball()
dx = 1
end
end
function draw()
background(255, 255, 255, 255)
for i = 1,#b do
pushStyle()
fill(b[i].colr)
ellipse(b[i].x, b[i].y, b[i].diameter)
popStyle()
b[1].x = b[1].x + b[2].x/5000
b[2].x = b[2].x – dx
end
end
Ball = class()
Ball = class()
function Ball:init(x)
self.y = y or math.random(0,HEIGHT)
self.x = x or math.random(0,WIDTH)
self.diameter = d or math.random(50,200)
self.colr = c or color(math.random(255), math.random(255), math.random(255), math.random(100,255))
end
And it even works perfectly but when I change the number of balls from 10 to 2 the speed of b2 decreases and when I increase the number of balls the speed of b2 increases.
Do you know why?
Sorry wrote Pasted Ball class() twice
I can’t see an obvious reason. Keep playing around and I’m sure you’ll solve it.
K fine thanks
function setup()
b = {}
for i = 1,10 do
b[i] = Ball()
dx = 1
end
end
function draw()
background(255, 255, 255, 255)
for i = 1,#b do
pushStyle()
fill(b[i].colr)
ellipse(b[i].x, b[i].y, b[i].diameter)
popStyle()
b[1].x = b[1].x + b[2].x/5000
b[2].x = b[2].x – dx
end
end
Ball = class()
function Ball:init(d, x, y, c)
self.x = x or math.random(0,WIDTH)
self.diameter = d or math.random(100,300)
self.y = y or math.random(0,HEIGHT)
self.colr = c or color(math.random(255), math.random(255), math.random(255), math.random(100,255))
end
This is the code
Thanks so much for the tutorials. I am 13 and I have understood everything up to here, but then I just found then ‘self.’ Stuff quite hard to actually use when I have so many other things going on.
I’m sure that I will figure it out, it just needs practice.
Cheers for the tutorials
Don’t worry. I am much older and have experience with several languages, and I didn’t understand self either!
Just keep working at it, and it will become clearer. Feeling stupid is quite normal when learning programming.
self.p=Physics(CIRCLE,{self.diameter},self.x,self.y) is self.p=Physics(CIRCLE,{self.diameter/2},self.x,self.y) its right in the code on pasteBin but not here in the explaination.
Amazing tutorials man, i’m loving em all btw!
for some reason when i do the very first section all i get is black and no circles btw i also put the function setup in the main with all the stuff ahead, and the Ball:init in the Ball class on a different tab
btw here is my code
BALL TAB
Ball = class()
function Ball:init(x,y,d,c)
— you can accept and set parameters here
self.x = x or math.random(0, WIDTH)
self.y = y or math.random(0, HEIGHT)
self.diameter = d or math.random(5, 100)
self.colr=c or color(255, 0, 0, 255)
end
MAIN TAB
function setup()
b = {}
for i=1,15 do
b[i]=Ball
end
end
i wish i can edit my post but the main tab and ball tab isn’t part of the code and there are no errors on the console