Paddle moved by gravity, elegance and passing vectors.

This is a little class I made today, I am wondering if anyone has any suggestions to streamline, etc. Also Lua is new to me and I am having trouble passing vectors to functions and using them in the functions. Any explanation on that would also be welcomed. And lastly a huge thank you to this forum, the wiki, and David Such’s tutorial for helping me get going!
Here is the Main “Class” (Are we calling them classes?)

function setup()
    pColor = color(67, 132, 128, 255)
    paddle = Paddle(WIDTH/2,5,80,15,pColor)    
    watch("Gravity.x")
    watch("Gravity.y")
end

-- This function gets called once every frame
function draw()
    
    background(0, 0, 0, 255)
    rect(WIDTH/2,HEIGHT - 20, 80,15)
    
    drawPaddle()

end

function drawPaddle()    
    paddle:draw()
end

And here is the Paddle class

Paddle = class()
    -- Josh Knox 
    -- 01/01/13 
    --
    -- This is a paddle class that moves based on gravity. 
    -- I would like to hear any suggestions as to the 'elegence' of the code.
    --
    -- I wanted to pass in posx and posy as position (vec2) but kept getting an nil error?
    -- And self.pos.x doesn't seem to work
    -- Anyone know why or how to pass in vector properly?
    
function Paddle:init(posx,posy,pWidth,pHeight,pColor)
    
    self.pWidth = pWidth
    self.pHeight = pHeight
    self.posx = posx
    self.posy = posy
    pos = vec2(posx,posy)
    size = vec2(pWidth,pHeight)
    
    self.pColor = pColor
end

function Paddle:draw()
    
    checkGrav()
    
    fill(pColor)   
    -- I really don't like having some of the varables self.blah or others not.  
    rect(pos.x,pos.y,self.pWidth,self.pHeight)

end
-- It just feels like there are alot of 'if's in this function...any ideas?
function checkGrav()
    -- This function checks boundaries first to avoid 'bouncing' on the edges
    if pos.x >= 0 and pos.x <= WIDTH - size.x then
        if Gravity.x > 0 then
            pos.x = pos.x + (20 * Gravity.x)
        end  
        if Gravity.x < 0 then
            pos.x = pos.x - (-20 * Gravity.x)
        end    
    end 
            
    if pos.x < 0 then
        pos.x = 0
    end
    
    if pos.x > WIDTH - size.x then
        pos.x = WIDTH - size.x
    end
end

function Paddle:touched(touch)
    -- Codea does not automatically call this method
end

Passing vec2 to a function works fine. If you still cant do it, post your code that does not work and sby will tell you where you get it wrong.

function passMeAvec2(v)
 print(v)
end

The main issue that I see in your code is a confusion between the various types of variable. These are:

  1. Global variables
  2. Local variables
  3. Instance variables

(These also apply to functions.)

Local and global variables are straightforward: a variable has a scope wherein it is defined and has meaning. Outside that scope, it is inaccessible. Unless otherwise stated, a variable has global scope in that it has meaning for the entire program. But a variable that is declared using local has scope only for the block in which it is defined.

(Where this can get slightly confusing is with reuse of variable names. So there can be variables with the same name but with different scopes. The rule is that the most recent declaration wins and all others are hidden.)

In your code you didn’t actually have any local variables so we’ll skip over that and pass to instance variables. When working with objects you can define variables that are bound to a particular instance of that object. In your code you have one object, Paddle, and one instance of that object, paddle. So paddle might have some attributes that are set for it (such as its position, size, colour) that could be different for a different instance of that object. If you had two paddles they could have different sizes. These are stored as instance variables and are prefixed with the word self. So in your code, self.posx is an example. Thus if you create two paddles, you can safely assign different positions to each.

However, in your code then not all things that should be instance variables are so. In particular, pos is a global variable. This means that if you had more than one paddle, all would be trying to use the same pos variable to store their position. This would cause chaos: each would be updating the same variable instead of their own position variables. So pos needs to be self.pos.

A similar story holds for functions. The checkGrav function should really be an instance function because it modifies instance variables. What it does to one paddle will be different to what it does to another paddle. The way to do this is to make it a method by defining it as Paddle:checkGrav and calling it as self:checkGrav (from within Paddle:draw, or paddle:checkGrav from the main program). What this does is to ensure that within that function call, the variable self refers to the instance of the object that is calling it. This means that self.pos now refers to the position of the paddle that is invoking the checkGrav routine.

Here’s my version of your code with the above put in place. As you see, I could pass in vectors with no problem so I don’t know what was causing the difficulties there. I also reduced the number of ifs in the checkGrav routine since you were doing the same thing on the inner if (the check for Gravity.x: -(-20*Gravity.x) = 20*Gravity.x). The first if in that routine could skip the check but this would be an optimisation step: I don’t know if doing the if makes it faster than always updating self.pos and correcting for endpoints afterwards.

function setup()
    pColor = color(67, 132, 128, 255)
    paddle = Paddle(vec2(WIDTH/2,5),vec2(80,15),pColor)    
    watch("Gravity.x")
    watch("Gravity.y")
end

-- This function gets called once every frame
function draw()

    background(0, 0, 0, 255)
    rect(WIDTH/2,HEIGHT - 20, 80,15)

    paddle:draw()

end

Paddle = class()
    -- Josh Knox 
    -- 01/01/13 
    --
    -- This is a paddle class that moves based on gravity. 
    -- I would like to hear any suggestions as to the 'elegence' of the code.
    --
    -- I wanted to pass in posx and posy as position (vec2) but kept getting an nil error?
    -- And self.pos.x doesn't seem to work
    -- Anyone know why or how to pass in vector properly?

function Paddle:init(pos,size,pColor)
    self.pos = pos
    self.size = size
    self.pColor = pColor
end

function Paddle:draw()

    self:checkGrav()

    fill(self.pColor)   
    -- I really don't like having some of the varables self.blah or others not.  
    rect(self.pos.x,self.pos.y,self.size.x,self.size.y)

end
-- It just feels like there are alot of 'if's in this function...any ideas?
function Paddle:checkGrav()
    -- This function checks boundaries first to avoid 'bouncing' on the edges
    if self.pos.x >= 0 and self.pos.x <= WIDTH - self.size.x then
        self.pos.x = self.pos.x + (20 * Gravity.x)
    end 

    if self.pos.x < 0 then
        self.pos.x = 0
    end

    if self.pos.x > WIDTH - self.size.x then
        self.pos.x = WIDTH - self.size.x
    end
end

function Paddle:touched(touch)
    -- Codea does not automatically call this method
end

Thank you so much Andrew!

I think my problem with passing the vec2 was that you don’t have to declare a type for most variables in Lua. (Are the vectors we use in codea specific to codea?)

And thanks for the explanation of scoping, and class methods. I understand these concepts, but its just a matter of applying them in our environment.

Thanks again! I am off to try out these changes!

The vectors are a “userdata” and thus special to Codea - but only in that the code that deals with them is compiled in to the binary and thus faster than if defined in pure lua. You could emulate them in pure lua quite easily.

The thing to remember about functions and variables in lua is that everything is a pointer and so there are no types to declare: at the time when the variable is passed, lua does not know or care what the eventual type will be.