Skip to content
Tags

8. Classes in Codea (2)

March 23, 2013

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

6e2a0eee937011e2aa3022000a9e2931_7

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

And the code is here or here.

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.

From → Programming

19 Comments
  1. 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

  2. Ken Duerden permalink

    Very useful. Thanks again.

  3. Saurabh permalink

    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.

      • Saurabh permalink

        Thanks a lot!!

      • That’s ok, hope you have lots of fun with Codea. It gets much easier once you understand the basics.

  4. Saurabh permalink

    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?

  5. Saurabh permalink

    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.

  6. Drannach permalink

    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!

  7. jlpt permalink

    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

  8. jlpt permalink

    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

  9. jlpt permalink

    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

Trackbacks & Pingbacks

  1. 85. A practical example showing the value of classes | coolcodea
  2. 169. Why tables and classes are so useful | coolcodea

Leave a comment