• Dear Cerberus X User!

    As we prepare to transition the forum ownership from Mike to Phil (TripleHead GmbH), we need your explicit consent to transfer your user data in accordance with our amended Terms and Rules in order to be compliant with data protection laws.

    Important: If you accept the amended Terms and Rules, you agree to the transfer of your user data to the future forum owner!

    Please read the new Terms and Rules below, check the box to agree, and click "Accept" to continue enjoying your Cerberus X Forum experience. The deadline for consent is April 5, 2024.

    Do not accept the amended Terms and Rules if you do not wish your personal data to be transferred to the future forum owner!

    Accepting ensures:

    - Continued access to your account with a short break for the actual transfer.

    - Retention of your data under the same terms.

    Without consent:

    - You don't have further access to your forum user account.

    - Your account and personal data will be deleted after April 5, 2024.

    - Public posts remain, but usernames indicating real identity will be anonymized. If you disagree with a fictitious name you have the option to contact us so we can find a name that is acceptable to you.

    We hope to keep you in our community and see you on the forum soon!

    All the best

    Your Cerberus X Team

OOoo

Wingnut

Well-known member
3rd Party Module Dev
Tutorial Author
Joined
Jan 2, 2020
Messages
1,414
As an exercise I converted a few Mojo1-examples that uses Object-Oriented programming as its logical structure.
While converting them into Mojo2 I noticed that you now have to pass the canvas around between everything to give access.

It forces you to add additional parameters (canvas:Canvas in this case) everywhere.
To show what I mean, here's an example (this is not the worst example but it is the one I have in front of me).

Is this normal OO behaviour to add "superflous" parameters like this or should I change the style?

Cerberus:
Strict
Import mojo2
Import monkey.math
Import monkey.random

Const GRAVITY:Float = 0.098
Global DW:Int,DH:Int
Global NumBalls:Int = 5

Function Main:Int()
    New MyApp 
    Return 0
End

Class MyApp Extends App

    Field canvas:Canvas
    Field ballArray:Ball[] 
  
    Method OnCreate:Int()     
        canvas = New Canvas
        DW = DeviceWidth() 
        DH = DeviceHeight()             
        ballArray = New Ball[NumBalls] 
        Seed = Millisecs() 
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i] = New Ball(0,Floor(Rnd(0.0,DW)),Floor(Rnd(0.0,DH)),Rnd(-5.0,5.0),Rnd(-5.0,5.0),1.0,0.001,10.0)
        Next         
        ' Lock to vertical blanking
        SetSwapInterval 1
        SetUpdateRate 0
        Return 0 
    End

    Method OnUpdate:Int()
        If KeyHit(KEY_ESCAPE) Then EndApp
        If TouchHit(0)
        For Local temp:Int = 0 Until 20
            NumBalls += 1
            ballArray = ballArray.Resize(NumBalls)
            ballArray[ballArray.Length - 1] = New Ball(0,TouchX(0),TouchY(0),Rnd(-5.0,5.0),Rnd(-5.0,5.0),1.0,0.001,10.0)
        Next
        End
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i].Update
            For Local j:Int = 0 Until ballArray.Length
                If (i <> j) Then BallVsBall(ballArray[i],ballArray[j])
            End
        End     
        Return 0         
    End

    Method OnRender:Int()     
        canvas.Clear 0,0,0             
        canvas.DrawText "Touch or left click for additional balls",5,5 ' +String(NumBalls)
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i].Render(canvas)
        End
        canvas.Flush
        Return 0         
    End
End

Class Ball

    Field kind:Int
    Field x:Float
    Field y:Float
    Field vx:Float
    Field vy:Float
    Field friction:Float
    Field mass:Float
    Field radius:Float
  
    Method New(k:Int,sx:Float,sy:Float,svx:Float,svy:Float,fric:Float,m:Float,r:Float)
        kind = k
        x = sx
        y = sy
        vx = svx
        vy = svy
        friction = fric
        mass = m
        radius = r
    End 
  
    Method Update:Void()
        vx = (vx * friction)                         
        vy = ((vy + GRAVITY) * friction)
        If kind <> 0 Then vx = 0 ; vy = 0
        x = (x + vx)             
        y = (y + vy)
        If ((x + radius) < 0.0) Then vx = -(vx)
        If (x > (DW - radius)) Then vx = -(vx) ; x = DW - radius
        If ((y + radius) < 0) Then vy = -(vy)     
        If (y > (DH - radius)) Then vy = -(vy) ; y = DH - radius
    End 
  
    Method Render:Void(canvas:Canvas)
        canvas.DrawOval(x - (radius / 2.0),y - (radius / 2.0),radius * 2.0,radius * 2.0) 
    End
End

Function BallVsBall:Void(b:Ball,b2:Ball)
    Local collisiondistance:Float = (b.radius + b2.radius)
    Local actualdistance:Float = GetDistance(b.x,b.y,b2.x,b2.y)
    If (actualdistance < collisiondistance)
        Local normal:Float = ATan2((b2.y - b.y),(b2.x - b.x))
        Local movedist1:Float = ((collisiondistance - actualdistance) * (b2.mass / Float((b.mass + b2.mass))))
        Local movedist2:Float = ((collisiondistance - actualdistance) * (b.mass / Float((b.mass + b2.mass))))
        b.x = (b.x + (movedist1 * Cos(normal + 180.0)))         
        b.y = (b.y + (movedist1 * Sin(normal + 180.0)))
        b2.x = (b2.x + (movedist2 * Cos(normal)))
        b2.y = (b2.y + (movedist2 * Sin(normal)))
        Local nx:Float = Cos(normal)                         
        Local ny:Float = Sin(normal)
        Local a1:Float = ((b.vx * nx) + (b.vy * ny))             
        Local a2:Float = ((b2.vx * nx) + (b2.vy * ny))
        Local opt:Float = ((2.0 * (a1 - a2)) / (b.mass + b2.mass))
        b.vx = (b.vx - (opt * b2.mass * nx))
        b.vy = (b.vy - (opt * b2.mass * ny))
        b2.vx = (b2.vx + (opt * b.mass * nx))
        b2.vy = (b2.vy + (opt * b.mass * ny)) 
    Endif
End

Function GetDistance:Float(x1:Float,y1:Float,x2:Float,y2:Float)
    Return Sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))
End

Screenshot 2020-11-27 at 04.49.02.png
 
Last edited:
Well as you are trying to access an object contained inside another, then you will have to pass that object as a parameter. Unless you define the canvas as global within the main class and access it via the class definition name and scope operator.
 
Or you store it outside thee main app in a global variable....
Or store it inside the object that will access it. Preferably when you create it as a parameter of your NEW call.
Both ways you have to pass/store it just once.
 
Well I first tried to do define the canvas as a global but somehow I failed a few times . I do not know why so I stopped doing that.
I'll try again, I seriously do not know why Global canvas:Canvas did not work across all classes.
 
What about the performance? Are both methods reasonable? I can't easily follow what happens behind the scenes when I juggle like this but my guess is that it is not terrible but global ought to be the better one if there's a difference.

Globals versus additional parameters.. both feel very secondary to me. I do not know what I expect from Object Orientation but code feels bloated and very quickly.

I might be wrong, I just wanted to hear what you have to say from a more experienced perspective.
 
I think I got confused also because the canvas parameter normally gets accessed through a notation like : somecanvas.somecanvasction(someparameters) and suddenly you're forced to put the left side inside the parameters instead.

Also I've been taught to avoid globals my whole life so It's stupendously hard for me to go against it.
Waiting for that aha moment now..
 
Global variable gives me either an error or empty screen.

Cerberus:
Strict
Import mojo2
Import monkey.math
Import monkey.random

Const GRAVITY:Float = 0.098
Global DW:Int,DH:Int
Global NumBalls:Int = 5
Global canvas:Canvas

Function Main:Int()
    New MyApp   
    Return 0
End

Class MyApp Extends App

    
    Field ballArray:Ball[]   
    
    Method OnCreate:Int()       
        ' canvas = New Canvas
        DW = DeviceWidth()   
        DH = DeviceHeight()               
        ballArray = New Ball[NumBalls]   
        Seed = Millisecs()   
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i] = New Ball(0,Floor(Rnd(0.0,DW)),Floor(Rnd(0.0,DH)),Rnd(-5.0,5.0),Rnd(-5.0,5.0),1.0,0.001,10.0)
        Next           
        ' Lock to vertical blanking
        SetSwapInterval 1
        SetUpdateRate 0
        Return 0   
    End

    Method OnUpdate:Int()
        If KeyHit(KEY_ESCAPE) Then EndApp
        If TouchHit(0)
        For Local temp:Int = 0 Until 20 ' 0 until 0 = 0!
            NumBalls += 1
            ballArray = ballArray.Resize(NumBalls)
            ballArray[ballArray.Length - 1] = New Ball(0,TouchX(0),TouchY(0),Rnd(-5.0,5.0),Rnd(-5.0,5.0),1.0,0.001,10.0)
        Next
        End
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i].Update
            For Local j:Int = 0 Until ballArray.Length
                If (i <> j) Then BallVsBall(ballArray[i],ballArray[j])
            End
        End       
        Return 0           
    End

    Method OnRender:Int()       
        canvas.Clear 0,0,0               
        canvas.DrawText "Touch or left click for additional balls",5,5 ' +String(NumBalls)
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i].Render()
        End
        canvas.Flush
        Return 0           
    End
End

Class Ball

    Field kind:Int
    Field x:Float
    Field y:Float
    Field vx:Float
    Field vy:Float
    Field friction:Float
    Field mass:Float
    Field radius:Float
    
    Method New(k:Int,sx:Float,sy:Float,svx:Float,svy:Float,fric:Float,m:Float,r:Float)
        kind = k
        x = sx
        y = sy
        vx = svx
        vy = svy
        friction = fric
        mass = m
        radius = r
    End   
    
    Method Update:Void()
        vx = (vx * friction)                           
        vy = ((vy + GRAVITY) * friction)
        If kind <> 0 Then vx = 0 ; vy = 0
        x = (x + vx)               
        y = (y + vy)
        If ((x + radius) < 0.0) Then vx = -(vx)
        If (x > (DW - radius)) Then vx = -(vx) ; x = DW - radius
        If ((y + radius) < 0) Then vy = -(vy)       
        If (y > (DH - radius)) Then vy = -(vy) ; y = DH - radius
    End   
    
    Method Render:Void()
        canvas.DrawOval(x - (radius / 2.0),y - (radius / 2.0),radius * 2.0,radius * 2.0)   
    End
End

Function BallVsBall:Void(b:Ball,b2:Ball)
    Local collisiondistance:Float = (b.radius + b2.radius)
    Local actualdistance:Float = GetDistance(b.x,b.y,b2.x,b2.y)
    If (actualdistance < collisiondistance)
        Local normal:Float = ATan2((b2.y - b.y),(b2.x - b.x))
        Local movedist1:Float = ((collisiondistance - actualdistance) * (b2.mass / Float((b.mass + b2.mass))))
        Local movedist2:Float = ((collisiondistance - actualdistance) * (b.mass / Float((b.mass + b2.mass))))
        b.x = (b.x + (movedist1 * Cos(normal + 180.0)))           
        b.y = (b.y + (movedist1 * Sin(normal + 180.0)))
        b2.x = (b2.x + (movedist2 * Cos(normal)))
        b2.y = (b2.y + (movedist2 * Sin(normal)))
        Local nx:Float = Cos(normal)                           
        Local ny:Float = Sin(normal)
        Local a1:Float = ((b.vx * nx) + (b.vy * ny))               
        Local a2:Float = ((b2.vx * nx) + (b2.vy * ny))
        Local opt:Float = ((2.0 * (a1 - a2)) / (b.mass + b2.mass))
        b.vx = (b.vx - (opt * b2.mass * nx))
        b.vy = (b.vy - (opt * b2.mass * ny))
        b2.vx = (b2.vx + (opt * b.mass * nx))
        b2.vy = (b2.vy + (opt * b.mass * ny))   
    Endif
End

Function GetDistance:Float(x1:Float,y1:Float,x2:Float,y2:Float)
    Return Sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))
End
 
I see now how it's the exact same problem as when you start with programming and you learn about operators vs functions :
"x + y" vs "Add(x,y)"

It never really matter how you add your numbers and both are equally fast, it's just a matter of elegance.
 
Global variable gives me either an error or empty screen
If I uncomment "canvas = New Canvas" it works here on html5 target.

Also changed the imports to cerberus.math cerberus.random
 
Take note that graphical elements such as the canvas type can only be initialised once the the main application object has been created. You will not be able to create or use any graphical method or function until this is completed. And as you are accessing a static data member there is no overhead. Also be aware of the difference of Private and Public within module scope when using static class variables.

Cerberus:
Strict
#CPP_MODE=2
Import mojo2
Import monkey.math
Import monkey.random

Const GRAVITY:Float = 0.098
Global DW:Int,DH:Int
Global NumBalls:Int = 5
' Global canvas:Canvas = New Canvas() ' This will not do anything.

Function Main:Int()
    New MyApp   
    Return 0
End

Class MyApp Extends App

    ' Global canvas:Canvas = New Canvas() ' This will not do anything.
    Global canvas:Canvas ' Static data member. Can be accessed by MyApp.canvas
        
    Field ballArray:Ball[]   
    
    Method OnCreate:Int()       
         canvas  = New Canvas()
        
        DW = DeviceWidth()   
        DH = DeviceHeight()               
        ballArray = New Ball[NumBalls]   
        Seed = Millisecs()   
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i] = New Ball(0,Floor(Rnd(0.0,DW)),Floor(Rnd(0.0,DH)),Rnd(-5.0,5.0),Rnd(-5.0,5.0),1.0,0.001,10.0)
        Next           
        ' Lock to vertical blanking
        SetSwapInterval 1
        SetUpdateRate 0
        Return 0   
    End

    Method OnUpdate:Int()
        If KeyHit(KEY_ESCAPE) Then EndApp
        If TouchHit(0)
        For Local temp:Int = 0 Until 20 ' 0 until 0 = 0!
            NumBalls += 1
            ballArray = ballArray.Resize(NumBalls)
            ballArray[ballArray.Length - 1] = New Ball(0,TouchX(0),TouchY(0),Rnd(-5.0,5.0),Rnd(-5.0,5.0),1.0,0.001,10.0)
        Next
        End
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i].Update
            For Local j:Int = 0 Until ballArray.Length
                If (i <> j) Then BallVsBall(ballArray[i],ballArray[j])
            End
        End       
        Return 0           
    End

    Method OnRender:Int()       
        canvas.Clear 0,0,0               
        canvas.DrawText "Touch or left click for additional balls",5,5 ' +String(NumBalls)
        For Local i:Int = 0 Until ballArray.Length
            ballArray[i].Render()
        End
        canvas.Flush
        Return 0           
    End
End

Class Ball

    Field kind:Int
    Field x:Float
    Field y:Float
    Field vx:Float
    Field vy:Float
    Field friction:Float
    Field mass:Float
    Field radius:Float
    
    Method New(k:Int,sx:Float,sy:Float,svx:Float,svy:Float,fric:Float,m:Float,r:Float)
        kind = k
        x = sx
        y = sy
        vx = svx
        vy = svy
        friction = fric
        mass = m
        radius = r
    End   
    
    Method Update:Void()
        vx = (vx * friction)                           
        vy = ((vy + GRAVITY) * friction)
        If kind <> 0 Then vx = 0 ; vy = 0
        x = (x + vx)               
        y = (y + vy)
        If ((x + radius) < 0.0) Then vx = -(vx)
        If (x > (DW - radius)) Then vx = -(vx) ; x = DW - radius
        If ((y + radius) < 0) Then vy = -(vy)       
        If (y > (DH - radius)) Then vy = -(vy) ; y = DH - radius
    End   
    
    Method Render:Void()
        MyApp.canvas.DrawOval(x - (radius / 2.0),y - (radius / 2.0),radius * 2.0,radius * 2.0)  ' Directly accessing the canvas object 
    End
End

Function BallVsBall:Void(b:Ball,b2:Ball)
    Local collisiondistance:Float = (b.radius + b2.radius)
    Local actualdistance:Float = GetDistance(b.x,b.y,b2.x,b2.y)
    If (actualdistance < collisiondistance)
        Local normal:Float = ATan2((b2.y - b.y),(b2.x - b.x))
        Local movedist1:Float = ((collisiondistance - actualdistance) * (b2.mass / Float((b.mass + b2.mass))))
        Local movedist2:Float = ((collisiondistance - actualdistance) * (b.mass / Float((b.mass + b2.mass))))
        b.x = (b.x + (movedist1 * Cos(normal + 180.0)))           
        b.y = (b.y + (movedist1 * Sin(normal + 180.0)))
        b2.x = (b2.x + (movedist2 * Cos(normal)))
        b2.y = (b2.y + (movedist2 * Sin(normal)))
        Local nx:Float = Cos(normal)                           
        Local ny:Float = Sin(normal)
        Local a1:Float = ((b.vx * nx) + (b.vy * ny))               
        Local a2:Float = ((b2.vx * nx) + (b2.vy * ny))
        Local opt:Float = ((2.0 * (a1 - a2)) / (b.mass + b2.mass))
        b.vx = (b.vx - (opt * b2.mass * nx))
        b.vy = (b.vy - (opt * b2.mass * ny))
        b2.vx = (b2.vx + (opt * b.mass * nx))
        b2.vy = (b2.vy + (opt * b.mass * ny))   
    Endif
End

Function GetDistance:Float(x1:Float,y1:Float,x2:Float,y2:Float)
    Return Sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))
End
 
Thanks for clearing that up, very much appreciated! It's coming along fine now.
 
Welcome to the wonders of OOP.

Do you know how to avoid passing an object everywhere? Create a Singleton!
(I'm writing the code below from memory, so it may have bugs in it!)

Code:
Class CanvasSingleton
   Global instance:CanvasSingleton
   Field canvas:Canvas

   Method new()
      canvas = new Canvas()
   End Method

   Function get:CanvasSingleton()
      if instance = null then instance = new CanvasSingleton()
      return instance
   End Function
End Class

With this, you just import that class anywhere you want and do a CanvasSingleton.get().canvas to access it.

(Yikes! I just saw this was more than an year ago... sorry!)
 
Thanks! Global variables in Classes will behave the same as static variables, right?
 
(Yikes! I just saw this was more than an year ago... sorry!)
I was just about to start coding so this was perfect timing. Terrible year this was..
 
Global variables in Classes will behave the same as static variables, right?
Global variables in Classes will behave the same as static members.
Fields in Classes will behave the same as static local variables in Functions.
I guess you are comparing to C++, right? The main aspect of Global variables in Classes is they don't need an instance and can be accessed from the Class identifier and from Class functions.
 
Last edited:
Fields in Classes will behave the same as static local variables in Functions.
How do you mean functions? I use them as static variables in methods too?
 
How do you mean functions? I use them as static variables in methods too?
Oh, that was misleading. Static local variables in Functions in C++ can be best emulated by Fields in Methods in CX. Not in CX Functions.
 
But you never actually define fields inside methods you define them in classes and use them in methods / functions, that's what you mean right?
 
Last edited:
Right. In C++ you can create a static variable in a method and it keeps its value so you can use it in the next call of the method or function. This is not possible in CX. The best way to get near this behaviour is to have a variable in the surrounding scope of the method, which is the class scope. For Class functions in CX you can use Class globals to keep the scope as small as possible. Well, there is always a bit of a trade-off.
 
Thanks, great help!
 
Back
Top Bottom