Getting Started with dmc-objects

This Quick Guide will give you a brief overview object-oriented programming using dmc-objects.

The intent is to get you up-and-running quickly through some simple examples. For more in-depth information about the module, check out the full !! file not found !!.

For reasons why you might use dmc-objects, see !! file not found !!.

Table of Contents

{toc:style=disc|indent=20px|minLevel=2|exclude=Table of Contents|printable=false}

Quick Overview

More often your classes will inherit from the class CoronaBase. This class is intended to have a visual representation so it is backed by a Corona Display Group. The Display Group is a very useful Corona element because you can add other Corona Display elements to it, you have all of the other qualities of a display group eg, numChildren, insert(), etc and you can do event handling and dispatching which are important for larger projects written object-orientedly.

\

So, in your mind, just imagine that you are working with a super advanced version of the Corona Display Group ... because you are. :)

Example 1: Your First Class

For this example we are going to be using the code in the file [^myfirstclass-example.zip].

The Custom Class File

At the root-level of your project, create a text file named myfirstclass.lua. Inside of it we will put the simplest code for creating a class with dmc_objects:

\

{code:title=myfirstclass.lua|language=none}

-- Imports

local Objects = require( "dmc_objects" )

--====================================================================--

-- Module Setup, Constants, & Support

-- setup some aliases to make code cleaner local inheritsFrom = Objects.inheritsFrom local CoronaBase = Objects.CoronaBase

--====================================================================--

-- My Custom Class

local MyFirstClass = inheritsFrom( CoronaBase )

return MyFirstClass




\\
\\

*That's it. You have created your first OO class using Lua and Corona!*
Granted, it's a very simple class structure, but it already has a lot of functionality as we'll soon see.

First, let's go over the code and mention some notable things:

\\
\\



*1. Import `dmc_objects` Module*

You import `dmc_objects` like any other Lua module by using the `require()` statement.

_Of course you will need to adjust the path if you don't store it at the project's root-level._

{code:language=none}
--====================================================================--
-- Imports
--====================================================================--

local Objects = require( "dmc_objects" )

2. Reference Object Hierarchy

dmc_objects has a few levels in the object hierarchy, but the one you're likely to use most often will be CoronaBase. I generally add these lines in the Setup area to make things a little cleaner, though it's not necessary.

If you decide to not put these lines in, you will have to adjust the references in the next part.

{code:language=none}

-- Module Setup, Constants, & Support

-- setup some aliases to make code cleaner local inheritsFrom = Objects.inheritsFrom local CoronaBase = Objects.CoronaBase



*3. Create Your Subclass*

Finally, we will create our subclass inheriting from `CoronaBase`. Here we have named our subclass `MyFirstClass`


{code:language=none}
--====================================================================--
-- My First Class
--====================================================================--

local MyFirstClass = inheritsFrom( CoronaBase )

4. Return the Class

Now that we have finished setup, we need to return a reference to our new class so that it works with Lua.

{code:language=none}

-- My First Class

local MyFirstClass = inheritsFrom( CoronaBase )

return MyFirstClass



\\
\\


*That's all there is. Next we'll put our class to use.*


### Testing Object Creation

To test out our new, very simple class, we'll create our `main.lua` file. We will do the typical module import, then create and manipulate some instances of our object class.

\\

{code:title=main.lua|language=none}
--====================================================================--
-- Imports
--====================================================================--

local MyFirstClass = require("myfirstclass")


--====================================================================--
-- Module Setup, Constants, & Support
--====================================================================--

local obj1, obj2
local tmp


--====================================================================--
-- Main
--====================================================================--

-- Steps:
-- create objects
-- add native Corona Display object
-- move the object around the screen


obj1 = MyFirstClass:new() -- instantiate our class, use method new()

tmp = display.newCircle( 0, 0, 30 ) -- create a new display object
tmp:setFillColor(128,128,128)
obj1:insert( tmp )  -- we have added our display object
obj1.x, obj1.y = 100, 200


-- do same with Obj2
obj2 = MyFirstClass:new()
tmp = display.newCircle( 0, 0, 20 )
tmp:setFillColor(255,255,255)
obj2:insert( tmp )
obj2.x = 100
obj2.y = 400


-- we can even use our objects in transitions!
transition.to( obj1, { time=1000, x=250, y=600 })
transition.to( obj2, { time=500, alpha=0.5, x=450, y=600 })
transition.to( obj2, { time=500, delay=750, alpha=1.0 })

\

{info:title=Just an Example} In my experience, display objects required for a class will usually be added inside of your class file. But, for this example, I added some outside to show that we already have a functional class, despite the fact we have written only 5 lines of code! [info]

\

{tip:title=Getter/Setter Magic} We are able to use Corona transitions with our objects just like regular display objects because of the getter/setter functionality built into dmc_objects. Without getters and setters we wouldn't have been able to do that. Very cool stuff! {tip}

\ \

Example 2: Adding Additional Class Behavior

Our second example is an app in which we control UFOs — we can create them, ask them to fly around the screen, and delete them with a simple tapping.

The entire project is in the file [^ufo-example.zip].

In this example, we will see more of how and where to incorporate other functionality typical to OO development so that the code is well structured.

Overview of a Complete Class File

The recommended way to structure object oriented code in dmc_objects is shown in the following image. Having a consistent structure gives a mental map of where everything is roughly located in source files. This is a big benefit for yourself and when working with other developers.

Take a moment to look at each of the sections; we will go over them in more detail below.

!dmc_objects-layout.png!

The Base Class File

We will start with the basic class outline as we did above, then fill it in for the different sections. The only thing we have changed here is the name of the class itself.

\

{code:title=ufo.lua|language=none}

-- Imports

local Objects = require( "dmc_objects" )

--====================================================================--

-- Module Setup, Constants, & Support

-- setup some aliases to make code cleaner local inheritsFrom = Objects.inheritsFrom local CoronaBase = Objects.CoronaBase

--====================================================================--

-- UFO Class

local UFO = inheritsFrom( CoronaBase )

return UFO



### Module Setup, Constants, & Support

The area _Module Setup, Constants, & Support_ is a good place to put _module-level_ global variables and functions which support the class.

Below in our example, you can see that we have made a reference to a global module _Math_ and also setup a couple of functions which will perform raw calculations for our UFO class.

\\

{code:title=ufo.lua|language=none}
--====================================================================--
-- Imports
--====================================================================--

local Objects = require( "dmc_objects" )


--====================================================================--
-- Module Setup, Constants, & Support
--====================================================================--

-- setup some aliases to make code cleaner
local inheritsFrom = Objects.inheritsFrom
local CoronaBase = Objects.CoronaBase


local rand = math.random


local function getRandomNumber( lower, upper )
    return rand( lower, upper )
end


-- calculate time for given distance and velocity
--
local function calculateTime( x1, y1, x2, y2, velocity )
    local distance, time

    -- calculate pythagorean
    distance = math.pow( (x1-x2), 2) + math.pow( (y1-y2), 2)
    distance = math.sqrt( distance )

    time = math.floor( distance / velocity *1000 ) -- in milliseconds

    return time
end


--====================================================================--
-- UFO Class
--====================================================================--

local UFO = inheritsFrom( CoronaBase )



return UFO

Class Constants

The next thing to add would be any class constants. These go after the line which creates our class called UFO; we need our class reference available so that we can attach things to it.

Here you can can see that we have added groups of constants: image dimensions, external references, UFO velocities, image assets and constants used for events.

This could even include references to sounds and videos.

\

{code:title=ufo.lua|language=none}

-- Imports

local Objects = require( "dmc_objects" )

--====================================================================--

-- Module Setup, Constants, & Support

--- !!!! --- Setup Content is Collapsed for this Example --- !!!!

--====================================================================--

-- UFO Class

local UFO = inheritsFrom( CoronaBase ) UFO.NAME = "Unidentified Flying Object"

--== Class constants

UFO.IMG_W = 110 UFO.IMG_H = 65

-- constants for speeds UFO.FAST = 'fast_speed_key' UFO.MEDIUM = 'medium_speed_key' UFO.SLOW = 'slow_speed_key'

-- speeds, pixels per second -- use constants for easier lookup UFO.SPEEDS = {} UFO.SPEEDS[UFO.FAST] = 350 UFO.SPEEDS[UFO.MEDIUM] = 150 UFO.SPEEDS[UFO.SLOW] = 50

-- images for each speed UFO.COOL_IMG = "assets/ufo_cool.png" UFO.WARM_IMG = "assets/ufo_warm.png" UFO.HOT_IMG = "assets/ufo_hot.png"

--== Event Constants UFO.EVENT = "ufo_event" UFO.TOUCHED = "ufo_touched_event"

return UFO





### Class Constructors and Destructors

Next we'll cover object creation and destruction. In `dmc_objects` this is handled in a segmented, methodical fashion and forms one of the bigger distinctions from other OO frameworks. Read on to find out why this is important.



#### Overview of Object Lifecycle

If you recall from the above example, we called `:new()` to create our objects, but we never had to implement that method in our code. `dmc_objects` uses this method "behind-the-scenes" to setup the core functionality of an object class and, of greater interest to you, _*provide several hooks which tie into the lifecycle of object customization*_.

This lifecycle is kind of like layers of an onion. In object creation, layers of functionality are added on one at a time. The following method hooks are the creation "layers":
# _init()
# _createView()
# _initComplete()

Conversely, in object destruction, this same functionality is removed _in reverse order_ using the following hooks:
# _undoInitComplete()
# _undoCreateView()
# _undoInit()

\\

This complete lifecycle is shown in the following graphic:

!dmc_objects-lifecycle.png!

\\
\\

{tip:title=Helpful Hooks}
It's easy to overlook the benefits of this, but in fact there are many reasons to have an object lifecycle separated with method hooks. They help:
* enforce the way to handle creation and destruction as recommended by Corona Labs
* play an important role when subclassing from other visual classes in Lua
* when implementing the MVC design pattern
* bring structure to the app for you and other developers
* avoid memory issues
{tip}


\\

Inside of the class file, this basic structure would look like the following:

\\


{code:title=ufo.lua|language=none}
--====================================================================--
-- Imports
--====================================================================--

local Objects = require( "dmc_objects" )


--====================================================================--
-- Module Setup, Constants, & Support
--====================================================================--

--- !!!!
--- Setup Content is Collapsed for this Example
--- !!!!


--====================================================================--
-- UFO Class
--====================================================================--

local UFO = inheritsFrom( CoronaBase )
UFO.NAME = "Unidentified Flying Object"

--== Class constants

--- !!!!
--- Class Constants Collapsed for this Example
--- !!!!



--== START: Setup DMC Objects


function UFO:_init( params )
end

function UFO:_undoInit()
end



function UFO:_createView()
end

function UFO:_undoCreateView()
end



function UFO:_initComplete()
end

function UFO:_undoInitComplete()
end


--== END: Setup DMC Objects



return UFO

\

Now that we have an overview of the object lifecycle, we'll go over each set of hooks one at a time.

\

init() / undoInit()

These hooks are intended to manipulate object properties.

For example, we might save incoming parameters or setup other property names. Property names could include those for which we might not yet have data, eg references to images, groups, etc.

Here in our example code we have added our properties. These include a reference to our animation transition, a pointer to our background, and a structure to hold references to our UFO images.

Note that the setup is the opposite of the teardown. As we saw above, teardown serves as the final step to remove object information before it's deleted.

\

{code:title=ufo.lua|language=none}

-- Imports

local Objects = require( "dmc_objects" )

--====================================================================--

-- Module Setup, Constants, & Support

--- !!!! --- Setup Content is Collapsed for this Example --- !!!!

--====================================================================--

-- UFO Class

local UFO = inheritsFrom( CoronaBase ) UFO.NAME = "Unidentified Flying Object"

--== Class constants

--- !!!! --- Class Constants Collapsed for this Example --- !!!!

--== START: Setup DMC Objects

-- initialize and list class properties

function UFO:_init( params )

self:superCall( "_init", params )
--==--

--== Basic Properties ==--

self._transition = nil -- handle to a currently running transition


--== Display Groups ==--

--== Object References ==--

self._ufo_bg = nil  -- background image, for taps
self._ufo_views = {} -- dictionary of image, keyed on constants

end

function UFO:_undoInit()

--== Object References ==--

self._ufo_bg = nil
self._ufo_views = nil

--== Display Groups ==--

--== Basic Properties ==--

self._transition = nil

--==--
self:superCall( "_undoInit" )

end

function UFO:_createView() end

function UFO:_undoCreateView() end

function UFO:_initComplete() end

function UFO:_undoInitComplete() end

--== END: Setup DMC Objects

return UFO





h5. Tips

*Organize Properties*

I like to organize my properties into three types:
# those which hold simple data like numbers, tables, strings (Basic Properties)
# those which hold references to other display groups (Display Groups)
# those which hold references to other objects (shapes, images, sounds, etc)

*List Out All Properties*

Even though setting properties to `nil` `_init()` might see pointless, having a list of all object properties helps to document all of the properties in your class. This is helpful to
# ensure that all properties are taken care of in setup/teardown
# other developers trying to figure out the class
# editors like Sublime Text so they can autocomplete your code


*Class Overrides*

When you implement these classes, ensure that you call the appropriate `:superCall()` method. In _creation methods_ this should be called first, and in _destruction methods_ this should be called last.

_Note that `_init()` is the only hook which can pass along parameters to the super class. Any params required in the other hooks should be set as properties on your object in `_init()`._


{code:title=ufo.lua|language=none}
function UFO:_init( params )
    self:superCall( "_init", params )
    --==--

    -- ^^^^^^^^^^^^ in creation methods, do superCall() first

end

function UFO:_undoInit()

    -- vvvvvvvvvvvv in destruction methods, do superCall() last:

    --==--
    self:superCall( "_undoInit" )
end

\

createView() / undoCreateView()

These hooks are intended to manipulate any elements used for the object's view/display.

These could be images, other display groups, shapes, etc.

Here in our example we have added four items – three images which change with the velocity of the UFO and a background which we will use to register touch events.

Again, notice how the functionality in each group of hooks is equal but opposite – in one we build up, in the other we tear down. This gives us a very easy visual check to make sure that the application is structured correctly, and that we are taking care of any memory issues before they become a problem.

\

{code:title=ufo.lua|language=none}

-- Imports

local Objects = require( "dmc_objects" )

--====================================================================--

-- Module Setup, Constants, & Support

--- !!!! --- Setup Content is Collapsed for this Example --- !!!!

--====================================================================--

-- UFO Class

local UFO = inheritsFrom( CoronaBase ) UFO.NAME = "Unidentified Flying Object"

--== Class constants

--- !!!! --- Class Constants Collapsed for this Example --- !!!!

--== START: Setup DMC Objects

-- initialize and list class properties

function UFO:_init( params ) -- !!! Collapsed for this example end

function UFO:_undoInit() -- !!! Collapsed for this example end

-- create view elements for UFO

function UFO:_createView()

self:superCall( "_createView" )
--==--

local ufo_views = self._ufo_views -- make code easier to read
local o


--== Setup background

o = display.newRect( 0, 0, UFO.IMG_W, UFO.IMG_H )
o:setReferencePoint(display.CenterReferencePoint)
o.x, o.y = 0, 0
o.alpha = 0.05 -- just enough to allow a tap

self:insert( o )
self._ufo_bg = o


--== Setup UFO Images

-- setup cool
o = display.newImageRect( UFO.COOL_IMG, UFO.IMG_W, UFO.IMG_H )
o.isVisible = false

self:insert( o )
ufo_views[UFO.SLOW] = o

-- setup warm
o = display.newImageRect( UFO.WARM_IMG, UFO.IMG_W, UFO.IMG_H )
o.isVisible = false

self:insert( o )
ufo_views[UFO.MEDIUM] = o

-- setup hot
o = display.newImageRect( UFO.HOT_IMG, UFO.IMG_W, UFO.IMG_H )
o.isVisible = false

self:insert( o )
ufo_views[UFO.FAST] = o

end

-- remove all display elements during destruction

function UFO:_undoCreateView()

local ufo_views = self._ufo_views -- make code easier to read
local o

--== Remove UFO Images

o = ufo_views[UFO.SLOW]
ufo_views[UFO.SLOW] = nil
o:removeSelf()

o = ufo_views[UFO.MEDIUM]
ufo_views[UFO.MEDIUM] = nil
o:removeSelf()

o = ufo_views[UFO.FAST]
ufo_views[UFO.FAST] = nil
o:removeSelf()


--== Remove Background

o = self._ufo_bg
self._ufo_bg = nil
o:removeSelf()


--==--
self:superCall( "_undoCreateView" )

end

function UFO:_initComplete() end

function UFO:_undoInitComplete() end

--== END: Setup DMC Objects

return UFO



\\



#### initComplete() / undoInitComplete()

*These hooks are intended to manipulate any other miscellaneous items or tasks.*

These could be event listeners, loading initial data, setting initial location, or emitting custom events.

Here in our example code we have handled the touch event on our background plus set the initial UFO view and location. On teardown we only need to reverse the event handler, but we've also made sure that our object isn't in the middle of an animation before we destroy it.

\\

{code:title=ufo.lua|language=none}
--====================================================================--
-- Imports
--====================================================================--

local Objects = require( "dmc_objects" )


--====================================================================--
-- Module Setup, Constants, & Support
--====================================================================--

--- !!!!
--- Setup Content is Collapsed for this Example
--- !!!!


--====================================================================--
-- UFO Class
--====================================================================--

local UFO = inheritsFrom( CoronaBase )
UFO.NAME = "Unidentified Flying Object"

--== Class constants

--- !!!!
--- Class Constants Collapsed for this Example
--- !!!!



--== START: Setup DMC Objects


-- initialize and list class properties
--
function UFO:_init( params )
    -- !!! Collapsed for this example
end

function UFO:_undoInit()
    -- !!! Collapsed for this example
end


-- create view elements for UFO
--
function UFO:_createView()
    -- !!! Collapsed for this example
end

-- remove all display elements during destruction
--
function UFO:_undoCreateView()
    -- !!! Collapsed for this example
end



function UFO:_initComplete()
    self:superCall( "_initComplete" )
    --==--

    local o

    -- watch for touches on background
    o = self._ufo_bg
    o:addEventListener( "touch", self )

    -- show our initial "resting" image
    self._ufo_views[UFO.SLOW].isVisible = true

    -- randomly select a location on the display
    self:_setRandomLocation()

end

function UFO:_undoInitComplete()

    local o

    o = self._ufo_bg
    o:removeEventListener( "touch", self )

    self:_stopAnimation()


    --==--
    self:superCall( "_undoInitComplete" )
end


--== END: Setup DMC Objects




return UFO

\

Class Methods (public, private and event handlers)

Now that we have made it through completing the object setup, the last part is to put in the rest of the methods and behaviors which form the bulk of the class implementation.

I like to organize these methods into three groups, shown here as the comments I use as separators:


--== Public Methods


--== Private Methods


--== Event Handlers

\

Given that, here is the rest of our implementation with methods organized by group:

\

{code:title=ufo.lua|language=none}

-- Imports

local Objects = require( "dmc_objects" )

--====================================================================--

-- Module Setup, Constants, & Support

--- !!!! --- Setup Content is Collapsed for this Example --- !!!!

--====================================================================--

-- UFO Class

local UFO = inheritsFrom( CoronaBase ) UFO.NAME = "Unidentified Flying Object"

--== Class constants

--- !!!! --- Class Constants Collapsed for this Example --- !!!!

--== START: Setup DMC Objects

--- !!!! --- DMC Setup has been collapsed for this example --- !!!!

--== END: Setup DMC Objects

--== Public Methods

-- animate to new, random location

function UFO:move( speed )

local coords, velocity, time

coords = self:_getRandomLocation()
velocity = UFO.SPEEDS[ speed ]
time = calculateTime( self.x, self.y, coords.x, coords.y, velocity )

self:_stopAnimation()
self:_updateView( speed )
self:_moveToLocation( coords.x, coords.y, time )

end

--== Private Methods

-- get random x,y coordinates

function UFO:_getRandomLocation()

local W, H = display.viewableContentWidth, display.viewableContentHeight

-- get number within our screen bounds
local x = getRandomNumber( 0+UFO.IMG_W/2, W-UFO.IMG_W/2 )
local y = getRandomNumber( 0+UFO.IMG_H/2, H-UFO.IMG_H/2 )

return { x=x, y=y }

end

-- give ufo new coordinates, instantly

function UFO:_setRandomLocation() local coords = self:_getRandomLocation() self.x, self.y = coords.x, coords.y end

-- update the view of the ship depending on its linear velocity

function UFO:_updateView( speed )

local views = self._ufo_views

-- turn off all of our views
views[UFO.SLOW].isVisible = false
views[UFO.MEDIUM].isVisible = false
views[UFO.FAST].isVisible = false

-- then select the next view to show
views[speed].isVisible = true

end

-- stop any current animation

function UFO:_stopAnimation() if self._transition ~= nil then transition.cancel( self._transition ) self._transition = nil end end

-- animate to new location

function UFO:_moveToLocation( x, y, time )

local f

-- callback when animation completes
f = function()
    self._transition = nil
    self:_updateView( UFO.SLOW ) -- return to "resting"
end

-- start new animation
self._transition = transition.to( self, { time=time, x=x, y=y, onComplete=f })

end

--== Event Handlers

-- handler for touch events

function UFO:touch( event ) if event.phase == 'ended' then self:_dispatchEvent( UFO.TOUCHED ) end return true end

-- event dispatch helper

function UFO:_dispatchEvent( e_type, data )

-- setup custom event
local e = {
    name = UFO.EVENT,
    type = e_type,
    target = self,
    data = data
}

self:dispatchEvent( e )

end

return UFO

```

\

This finishes up our UFO class.

Download the files so you can see now main.lua interacts with the objects and then run it in the Corona Simulator.

\

Further Exploration

{tip:title=More Examples} There are other complete examples which can be run on the Corona simulator or a mobile device. These are located in the folder examples/dmc_objects/ which comes bundled with DMC Corona Library.

Some of the examples in the folder are examples which have originally come from Corona Labs, but they have been modified to be object-oriented using the ideas/code in dmc_objects. {tip}