Changing variables from another class...

Okay. So I’m trying to learn to code in Codea but I feel there’s a lack of tutorials so I’ve just been trying to figure things out.
My goal is simply to move my character using buttons.
I have a LeftBtn class and Character class and I’m wondering how to change the x position of the character from the button class as I don’t want my Main class to deal with my character’s movements. I like a clean Main class.
Here’s My code:

MAIN CLASS


-- Use this function to perform your initial setup
function setup()
    displayMode(FULLSCREEN)
    char = Character()
    charX = WIDTH/2
    charY = HEIGHT/2
    char:init(charX,charY)
    LeftBtn:init(WIDTH/4-200,HEIGHT/4,100,100)
    RightBtn:init(WIDTH/4-50,HEIGHT/4,100,100)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(255, 255, 255, 255)

    -- This sets the line thickness
    strokeWidth(5)
    textSize(20)
    text(charX, 500, 500)

    -- Do your drawing here
    char:draw()
    LeftBtn:draw()
    RightBtn:draw()
    
end

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

CHARACTER CLASS


Character = class()

function Character:init(x,y)
    -- you can accept and set parameters here
    self.x = x
    self.y = y
end

function Character:draw()
    -- Codea does not automatically call this method
    sprite("Planet Cute:Character Boy", self.x, self.y, 101, 171)
end

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

LEFTBTN CLASS

LeftBtn = class()

function LeftBtn:init(x,y,w,h)
    -- you can accept and set parameters here
    self.x = x
    self.y = y
    self.w = w
    self.h = h
end

function LeftBtn:draw()
    -- Codea does not automatically call this method
    fill(87, 87, 87, 50)
    rectMode(CENTER)
    noStroke()
    rect(self.x, self.y, self.w, self.h)
    fill(0)
    font("MarkerFelt-Wide")
    fontSize(80)
    text("L", self.x, self.y)
    
end

function LeftBtn:touched(touch)
    -- Codea does not automatically call this method
    if touch.state == BEGAN then character.x = character.x - 4 else end
end

(Firstly, code blocks are delimited by three tildes, not three dashes)

Your code updates the x field of the character table. But there is not character table. There is a Character class (note the capital) which is really just a table in disguise, and a char object which is an instance of the Character class, but again is just a table in disguise. What you really want to update is char.x so you could put char.x in place of character.x.

But I wouldn’t do that. I’d rather make LeftBtn not know anything about what it is acting on - it makes it easier to change things later on. So when initialising LeftBtn, I’d pass it the char object:

LeftBtn:init(WIDTH/4-200,HEIGHT/4,100,100,char)

then in the initialisation code:

function LeftBtn:init(x,y,w,h,o)
    self.acton = o

and in the touched function

function LeftBtn:touched(touch)
    -- Codea does not automatically call this method
    if touch.state == BEGAN then self.acton:move(-4) end
end

finally, the character has to know what to do:

function Character:move(x)
    self.x = self.x + x
end

In fact, I’d probably go a bit further and define one button class of which both the left and right were instances, which would mean passing the text as well. I’d also make it even less tied to the Character class and send a callback function, but that would need a bit more restructuring.

The last point to make is that charX never gets updated. This is because in the line (using my code, but it’s the same in yours) self.x = self.x + x then this assigns to self.x (a pointer to) the result of the computation self.x + x. What it does not do is update the value that self.x was pointing to before. So charX happily goes on pointing to WIDTH/2 while self.x wanders all over the screen.

You responded so fast that I can only assume ur awesome. Thank u, let me try out what u suggested :slight_smile:

I think I did what you said but nothing happens :\
DId I miss something? I don’t think the left button’s “touch” function are being called but I clearly put it in the Main class, right?

MAIN



-- Use this function to perform your initial setup
function setup()
    displayMode(FULLSCREEN)
    char = Character()
    char:init(WIDTH/2,HEIGHT/2)
    LeftBtn:init(WIDTH/4-200,HEIGHT/4,100,100,char)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(255, 255, 255, 255)

    -- This sets the line thickness
    strokeWidth(5)
    textSize(20)

    -- Do your drawing here
    char:draw()
    LeftBtn:draw()
    
end

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

CHARACTER

Character = class()

function Character:init(x,y)
    -- you can accept and set parameters here
    self.x = x
    self.y = y
end

function Character:draw()
    -- Codea does not automatically call this method
    sprite("Planet Cute:Character Boy", self.x, self.y, 101, 171)
end

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

function Character:move(x)
    self.x = self.x + x
end

LEFTBTN

LeftBtn = class()

function LeftBtn:init(x,y,w,h,o)
    -- you can accept and set parameters here
    self.acton = o
    self.x = x
    self.y = y
    self.w = w
    self.h = h
end

function LeftBtn:draw()
    -- Codea does not automatically call this method
    fill(87, 87, 87, 50)
    rectMode(CENTER)
    noStroke()
    rect(self.x, self.y, self.w, self.h)
    fill(0)
    font("MarkerFelt-Wide")
    fontSize(80)
    text("L", self.x, self.y)
    
end

function LeftBtn:touched(touch)
    -- Codea does not automatically call this method
    if touch.state == BEGAN then self.acton:move(-4) end
end

It seems like it should work :\

I usually put a print() statement in the function I am testing to see if it is called. Really helps !

How about something like the following? I used the mesh button class provided by @Vega. I suggest you put the Button() class in a separate tab.

Regarding your comment on tutorials, there are some over at http://codeatuts.blogspot.com.au/ I will add this code to the Tutorials with some supplementary explanations.

At the moment the ship only moves when you tap the button. Bonus points if you modify this code so that the ship moves as long as you hold the button down.



--# Main


-- Use this function to perform your initial setup

function setup()

   -- This block of code is optional. We just include it in 
   -- every project so that we have a method of version control.

   version = 1.0

   saveProjectInfo("Description", "Move Object Demonstration")
   saveProjectInfo("Author", "Reefwing Software")
   saveProjectInfo("Date", "11th July 2012")
   saveProjectInfo("Version", version)

   print("MoveShip v"..version.."\
")

   -- Initialise the co-ordinates of your "Ship"
   -- These will be updated by the action methods 
   -- associated with each directional button.

   shipPosition = vec2(WIDTH/2, HEIGHT/2)

   -- This slider parameter will control the ship "speed"
   -- The larger the number the further the ship will 
   -- move with each button tap.
   --
   -- The format of this function is: 
   -- parameter("name", min value, max value, init value)

   parameter("shipSpeed", 1, 10, 4)

   -- Define the four Buttons used to move the ship
   -- They wont be visible until you draw() them.
   -- Note that 50 pixels is the minimum height for the default
   -- button font size.

   local mButtonSize = vec2(100, 50)
   local mLocX = WIDTH - 250
   local mLocY = 100

   leftButton = Button("Left", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   leftButton.action = function() leftButtonTapped() end

   mLocX = mLocX + 150
   rightButton = Button("Right", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   rightButton.action = function() rightButtonTapped() end

   mLocX = mLocX - 75
   mLocY = mLocY + 60
   upButton = Button("Up", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   upButton.action = function() upButtonTapped() end

   mLocY = mLocY - 120
   downButton = Button("Down", vec2(mLocX, mLocY), mButtonSize.x, mButtonSize.y)
   downButton.action = function() downButtonTapped() end

   -- Assign the colours that you want to use in your game.

   blackColour = color(0,0,0)

end

-- This function gets called once every frame
-- Codea will attempt to call draw() 60 times per second, it
-- can be much less than this if you have a lot going on in your code.

function draw()

   -- This sets a black background color 

   background(blackColour)

   -- Do your drawing here
   -- Draw your ship at the current co-ordinates stored in shipPosition

   sprite("Tyrian Remastered:Boss D", shipPosition.x, shipPosition.y)

   -- Draw the four directional buttons

   leftButton:draw()
   rightButton:draw()
   upButton:draw()
   downButton:draw()

end

-- Button action methods

function leftButtonTapped()

   -- Update ship position as long as the ship isnt off the screen.
   -- math.max() returns the maximum value of the arguments, so
   -- if shipPosition < 0 it will set it to 0.

   shipPosition.x = math.max(shipPosition.x - shipSpeed, 0)

end

function rightButtonTapped()

   -- Update ship position as long as the ship isnt off the screen.
   -- math.min() returns the minimum value of the arguments, so
   -- if shipPosition > WIDTH it will set it to WIDTH.

   shipPosition.x = math.min(shipPosition.x + shipSpeed, WIDTH)

end

function upButtonTapped()
   shipPosition.y = math.min(shipPosition.y + shipSpeed, HEIGHT)
end

function downButtonTapped()
   shipPosition.y = math.max(shipPosition.y - shipSpeed, 0)
end

-- Handle screen touches

-- Note that you need to pass any touches through
-- to your button touch handlers.

function touched(touch)
   leftButton:touched(touch)
   rightButton:touched(touch)
   upButton:touched(touch)
   downButton:touched(touch)
end

--# Button
Button = class()

-- Mesh Button Class courtesy of @Vega
-- 26 May 2012
--
-- Modified: Call Back Functionality added
--           pushStyle() & popStyle() added to draw()
--
-- Version 1.1

function Button:init(text,location,width,height)
   self.state = "normal"
   self.text = text
   self.textColor = color(255,255,255,192)
   self.location = location
   self.width = width
   self.height = height
   self.visible = true
   self.fontSize = 28
   self.font = "ArialRoundedMTBold"
   self.color1 = color(255, 255, 255, 96)
   self.color2 = color(128,128,128,32)
   self.presscolor1 = color(192, 224, 224, 128)
   self.presscolor2 = color(96, 192, 224, 128)
   self.verts = self:createVerts(self.width, self.height)
   self.myMesh = mesh()
   self.myMesh.vertices = triangulate(self.verts)
   self.vertColor = {}
   self:recolor()
   self.action = nil
end

function Button:setColors(c1,c2,p1,p2)
   self.color1 = c1
   self.color2 = c2
   self.presscolor1 = p1
   self.presscolor2 = p2
end

function Button:textOptions(fn, sz, col)
   self.font = fn
   self.fontSize = sz
   self.textColor = col
end

function Button:draw()
   pushStyle()
   if self.visible == true then
       self:recolor()
       pushMatrix()
       translate(self.location.x,self.location.y)
       self.myMesh:draw()
       fill(self.textColor)
       fontSize(self.fontSize)
       font(self.font)
       text(self.text, self.width/2,self.height/2)
       self:drawLines(self.verts)
       popMatrix()
   end
   popStyle()
end

function Button:touched(touch)
   if self.visible then
       if touch.x >= self.location.x and touch.x <= self.location.x + self.width and touch.y >= self.location.y and touch.y <= self.location.y + self.height then
           if touch.state == BEGAN then
               self.state = "pressing"
               sound(SOUND_HIT, 14227)
           elseif touch.state == ENDED then
               if self.state == "pressing" then
                   self.state = "normal"
               end
               if self.action then
                   self.action()
               end
           end
       else
           self.state = "normal"
       end
   end
end

function Button:createVerts(w,h)
   local r
   local v = {}
   if w > 100 or h > 100 then
       if w>=h then r = math.round(h/100) else r = math.round(w/100) end
   else
       r = 1
   end
   v[1] = vec2(w,6*r)
   v[2] = vec2(w-r,4*r)
   v[3] = vec2(w-2*r,2*r)
   v[4] = vec2(w-4*r,r)
   v[5] = vec2(w-6*r,0)
   v[6] = vec2(6*r,0)
   v[7] = vec2(4*r,r)
   v[8] = vec2(2*r,2*r)
   v[9] = vec2(r,4*r)
   v[10] = vec2(0,6*r)
   v[11] = vec2(0,h-6*r)
   v[12] = vec2(r,h-4*r)
   v[13] = vec2(2*r,h-2*r)
   v[14] = vec2(4*r,h-r)
   v[15] = vec2(6*r,h)
   v[16] = vec2(w-6*r,h)
   v[17] = vec2(w-4*r,h-r)
   v[18] = vec2(w-2*r,h-2*r)
   v[19] = vec2(w-r,h-4*r)
   v[20] = vec2(w,h-6*r)
   return v
end

function Button:drawLines(v)
   noSmooth()
   strokeWidth(1)
   stroke(0, 0, 0, 192)
   for i=1, #v-1 do
       line(v[i].x,v[i].y,v[i+1].x,v[i+1].y)
   end
   line(v[#v].x,v[#v].y,v[1].x,v[1].y)   
end

function Button:recolor()
   local lt, dk
   if self.state == "normal" then 
       lt = self.color1
       dk = self.color2
   else
       lt = self.presscolor1
       dk = self.presscolor2
   end
   for i=1,3 * #self.verts - 6 do
       if self.myMesh.vertices[i].y > self.height/2 then
           self.vertColor[i] = lt
       else
           self.vertColor[i] = dk
       end
   end
   self.myMesh.colors = self.vertColor
end

function math.round(num)

   -- math.round function courtesy of @Vega

   return math.floor(num + 0.5)

end

Thank u. I figured out a way to do it but simply forgot to update it here. I will, however, try out what u suggested as the code greatly differs from mine. (kinda makes mine look too simple and mediocre)

@RichGala1, nice if you update your code here. Thanks in advance (I’m a newbie too) :smiley:

Hi, @Reefwing. Thanks for using the button mesh class and adding some functionality. When I made that I promised to update with callbacks and such and never got around to it. I think the way you did works pretty well.

@RichGala1 - nothing wrong with simple if it gets the job done. Well done on sorting it out on your own, that is one of the joys of coding. Most of the code in my solution is the general button class. Actually moving the Sprite doesn’t take much code at all.

@Vega - great button class, I am using it exclusively now, along with some of the other code you have contributed. I used the call back pattern from @Simeon’s button class so I cant take any credit for it.

how does the global “local” thing work?

Global variables are available throughout your code. If you dont specify then a variable is global.

If you specify a variable as local (by using the local keyword) then the variable is only available in the block where they are declared. A block can be the body of a control structure, the body of a function, or a chunk (unit of execution). For example, in the contrived example below:

  1. a is a global variable, available throughout your App.
  2. b is a local variable available only within someFunction().
  3. c is a local variable only available inside the while loop. Useless I know in this example.

You should try to use local variables as much as possible, access to them is much faster than for globals and using globals can make your code harder to read and maintain.

function someFunction()

    a = 0
    local b = 10

    while b > 0 do
        local c = 0
        b = b - 1
    end

end