Overview:
Autoscaling retina text with a unified API, embossed option, automatic reference pointing and repositioning.
----
Hey guys,
I'm sharing this simple text class because I've personally run into a lot of trouble getting text to scale and align perfectly in Corona over many screen sizes. I'd assume most of you are using some custom workarounds already!
This wraps display.newText and display.newEmbossedText text objects into a meta-class and custom scales them to ensure correct sizing and resolution on any screen. This means you can use one function for all your text-creation needs, and have a unified API (no difference between embossed and standard text), automatic realignment when moving/modifying text, and consistent behavior between all types of text.
In other words, how I thought text should work in Corona by default, but doesn't seem to.
Updates:
1 2 3 4 5 | v1.1 - Removes the .view property for consistency with the new Widget apis. Please note that upgrading to this version will require you to update your existing code. Once you do it will make everything simpler and brings several other improvements. v1.0 - Initial public release |
Creating text:
1 2 3 4 5 6 7 8 9 10 11 12 13 | local txt = require "txt" -- Instantiation: all trailing parameters are optional txt.new( text, x, y, font, size, refPoint, width, height ) txt.newEmbossed( text, x, y, font, size, refPoint, width, height ) ... local text1 = txt.new("Top-Left aligned", 10, 20, "HelveticaNeue", 15) local text2 = txt.new("Center aligned", 10, 40, "HelveticaNeue", 15, display.CenterReferencePoint) local text3 = txt.newEmbossed("Embossed text", 10, 60, "HelveticaNeue", 15) local text4 = txt.new("Multiline text\nThe width and height are scaled too!", 10, 80, "HelveticaNeue", 15, display.TopLeftReferencePoint, 200, 50) |
Functions & attributes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | -- Accessed just like a normal display object in 1.1 (no .view property) group:insert( text1 ) text1:setText( newText ) text1:position( [x, y] ) text1:setReference() --Returns the object to its initial reference point (self.reference) text1:removeSelf() text1.reference --Get the object's last stored reference point text1.isEmbossed --If this is embossed text text1.ox, text1.oy --The original x/y coords the object was initialized with -- You can also access all the standard text properties text1.x text1.alpha |
It's pretty barebones so feel free to customize. I hope this helps :)
This worked perfectly for me but in recent daily builds has stopped working
I now notice it has been taken off GitHub. Anyone else seen any issues?
Thanks for pointing this out! Looks like I managed to break the link. I've restored it and also posted an update. It changes how the objects work (no more .view property), but it should work with new builds and make it quite a bit easier to use once you've made the change.
Let me know if you have any questions about it.
Thanks for updating
Whats the best way of handling dynamic text as I find the only way to get, for example, a score that updates on enterframe is to remove and reapply to same position rather than just settext.
The reason I do this is that it moves slightly irrespective of what reference point I set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | local distanceString = string.format("%s: %i", "Distance", Globals.distance) -- Create the text for the distance score running in the top left corner myDistance = txt.new(distanceString , 0.05 * _W + _originX, _originY, "OogieBoogie", fontSize, display.TopLeftReferencePoint ) myDistance:setTextColor(255,255,255 ) -- later in the game loop I try updating with Globals.distance = Globals.distance +2 local distanceString = string.format("%s: %i", "Distance", Globals.distance) myDistance:setText(distanceString) -- but this moves so I have to do this instead which to be seems inefficient but keeps position locked -- Need to update the text of the distance score so remove the current instance myDistance:removeSelf() -- Create the distance string local distanceString = string.format("%s: %i", "Distance", Globals.distance) -- Calculate the position based on the former myDistance = txt.new(distanceString , 0.05 * _W + _originX, _originY, "OogieBoogie", fontSize ) myDistance:setTextColor(255,255,255 ) myDistance:setText(distanceString) |
It looks like I only had text repositioning working for embossed text in that last release. I've updated now so that txt.new should now properly realign automatically, too.
So you can just use this now:
1 | myDistance:setText(distanceString) |
Just a quick tip, it defaults to the TopLeftReferencePoint, so you don't need to pass that parameter unless you want to specify a different one.
Thanks ever so much for your swift response but that seems to have screwed by simple example. I can see that the text is positioned but everything else moves to the centre of the screen.
Is it right that you v1.1.1 is now 12 lines shorter than v1.1 as it seems to have some undesirable effects
Also I am unable in either version to add to a display object under Storyboard but trying to work out whether that is me or not
Hope this helps
Thanks ever so much for your swift response but that seems to have screwed by simple example. I can see that the text is positioned but everything else moves to the centre of the screen.
Is it right that you v1.1.1 is now 12 lines shorter than v1.1 as it seems to have some undesirable effects
Also I am unable in either version to add to a display object under Storyboard but trying to work out whether that is me or not
Hope this helps
It is, I simplified some things. Can you do me a favor and try removing the if statement on line 39? Leave the contents, just remove the condition. That's the only thing I can think of that would have broken something else for you.
The update seems fine with my code, you mean the rest of your txt objects move to the centre, can you elaborate here?
For storyboard, just make sure you're inserting just the object without the .view property, otherwise, I'd need to look at the code.
That made a difference to the objects moving to the centre but still not quite right.
I get an error of:
txt.lua:60: attempt to call method 'changeText' (a nil value)
stack traceback:
[C]: in function 'changeText'
txt.lua:60: in function 'setText'
main.lua:64: in function main.lua:58>
This has occurred with your latest code.
My code is the following if you want to try and replicate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | display.setStatusBar( display.HiddenStatusBar ) -- Pull in the text module for handling dynamic scaling of text local txt = require "txt" -- Create our display objects to ensure we can keep specific groupings together which -- is particular relevant on the depth calculations textObjects= display.newGroup() screenGroup = display.newGroup() -- Get display device width and height local _originX = display.screenOriginX local _originY = display.screenOriginY local _W = display.contentWidth - (_originX * 2) local _H = display.contentHeight - (_originY * 2) best = 0 fontSize = 14 distance = 0 -- Add this to the string that will appear in the top left hand corner local distanceString = string.format("%s: %i", "Distance", distance) -- Create the text for the distance score running in the top left corner myDistance = txt.new(distanceString , 0.05 * _W + _originX, _originY, "HelveticaNeue", fontSize) myDistance:setTextColor(255,255,255 ) -- Create the text for the best score previously achieved bestString = string.format("%s: %i", "Best", best) bestText = txt.new(bestString , 0.77 * _W + _originX, _originY, "HelveticaNeue", fontSize ) bestText:setTextColor(255,255,255 ) textObjects:insert(myDistance) textObjects:insert(bestText) -- Now add our texts to the main display group screenGroup:insert(textObjects) -- Create the pause button in the bottom corner pauseButtonText = txt.new("Pause" , 0.88 * _W + _originX, _H - 35 + _originY, "HelveticaNeue", fontSize ) pauseButtonText:setTextColor(255,255,255 ) pauseButtonText.name = "pausebutton_tapped" -- Determine the state of the game music and update the label accordingly local string = "Music On" -- Now create the sound button soundOnOffText = txt.new(string , 0.70 * _W + _originX, _H - 35 + _originY, "HelveticaNeue", fontSize ) soundOnOffText:setTextColor(255,255,255 ) -- Add the two buttons to the view textObjects:insert(pauseButtonText) textObjects:insert(soundOnOffText) -- The main game loop which keeps track of everthing in the game function gameLoop(event) -- During this pahse increment the distance travelled. An arbitary number at this point distance = distance + 2 -- Create the distance string local distanceString = string.format("%s: %i", "Distance", distance) myDistance:setText(distanceString) textObjects:insert(myDistance.view) end -- Add the listener for our main game loop Runtime:addEventListener("enterFrame",gameLoop) |
Hmm, well you should remove this line entirely from the gameLoop function:
1 | textObjects:insert(myDistance.view) |
Then grab v1.1.2 and hopefully that'll work for you.
Thanks for your feedback but I'm still getting the same error on line 60 of v1.1.2 The positioning is better though. I'm using daily build Version 2012.760 (2012.12.8)
main.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | display.setStatusBar( display.HiddenStatusBar ) -- Pull in the text module for handling dynamic scaling of text local txt = require "txt" -- Create our display objects to ensure we can keep specific groupings together which -- is particular relevant on the depth calculations textObjects= display.newGroup() screenGroup = display.newGroup() -- Get display device width and height local _originX = display.screenOriginX local _originY = display.screenOriginY local _W = display.contentWidth - (_originX * 2) local _H = display.contentHeight - (_originY * 2) best = 0 fontSize = 14 distance = 0 -- Add this to the string that will appear in the top left hand corner local distanceString = string.format("%s: %i", "Distance", distance) -- Create the text for the distance score running in the top left corner myDistance = txt.new(distanceString , 0.05 * _W + _originX, _originY , "HelveticaNeue", fontSize) myDistance:setTextColor(255,255,255 ) -- Create the text for the best score previously achieved bestString = string.format("%s: %i", "Best", best) bestText = txt.new(bestString , 0.77 * _W + _originX, _originY, "HelveticaNeue", fontSize ) bestText:setTextColor(255,255,255 ) textObjects:insert(myDistance) textObjects:insert(bestText) -- Determine the state of the game music and update the label accordingly local string = "Music On" -- Now create the sound button soundOnOffText = txt.new(string , 0.70 * _W + _originX, _H + _originY - 30, "HelveticaNeue", fontSize ) soundOnOffText:setTextColor(255,255,255 ) -- Create the pause button in the bottom corner pauseButtonText = txt.new("Pause" , 0.88 * _W + _originX, _H + _originY- 30, "HelveticaNeue", fontSize ) pauseButtonText:setTextColor(255,255,255 ) pauseButtonText.name = "pausebutton_tapped" -- Add the two buttons to the view textObjects:insert(pauseButtonText) textObjects:insert(soundOnOffText) -- Now add our texts to the main display group screenGroup:insert(textObjects) -- The main game loop which keeps track of everthing in the game function gameLoop(event) -- During this pahse increment the distance travelled. An arbitary number at this point distance = distance + 2 -- Create the distance string local distanceString = string.format("%s: %i", "Distance", distance) myDistance:setText(distanceString) end -- Add the listener for our main game loop Runtime:addEventListener("enterFrame",gameLoop) |
txt.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | -------- -- txt Class v1.1.2 -- by Kyle Coburn -- 4 March 2012 ---- -- For the latest updates & support/feedback: -- http://developer.anscamobile.com/code/dynamic-resolution-retina-text-class ---- -- Usage: -- local txt = require "txt" -- -- txt.new( text, x, y, font, size, refPoint, width, height ) -- txt.newEmbossed( text, x, y, font, size, refPoint, width, height ) -- -- All trailing parameters are optional -------- local txt = {}; local function createTxt(text, x, y, font, size, refPoint, wd, ht, embossed) local t; local nx, ny = x or 0, y or 0; local newSize = size or 18; local newRef = refPoint or display.TopLeftReferencePoint; if (wd and ht) then if (embossed) then print("WARNING: Embossed text objects do not currently support multiline text.") t = display.newEmbossedText(text, nx, ny, font, newSize); else t = display.newRetinaText(text, nx, ny, wd, ht, font, newSize); end elseif (embossed) then t = display.newEmbossedText(text, nx, ny, font, newSize); else t = display.newRetinaText(text, nx, ny, font, newSize); end t:setReferencePoint(newRef); t.x, t.y = nx, ny; t.ox, t.oy = nx, ny; t.reference = newRef; t.isEmbossed = embossed; t.changeText = t.setText; function t:position(x, y) self:setReference(); self.x, self.y = x or self.ox, y or self.oy; if (embossed) then self:setReference(); end end function t:setReference() self:setReferencePoint(self.reference); end function t:setText(string) self:changeText(string); self:position(); end return t; end function txt.new(text, x, y, font, size, refPoint, width, height) return createTxt(text, x, y, font, size, refPoint, width, height, false); end function txt.newEmbossed(text, x, y, font, size, refPoint, width, height) return createTxt(text, x, y, font, size, refPoint, width, height, true); end return txt; |
I'm really not sure then, sorry :\
I've also been using the latest daily builds and am not able to replicate the issue.. It sounds like a problem of newRetinaText not having the setText method, but that should only occur on older builds.
If you still can't get it then I guess your best bet is to revert to the standard methods.
Thanks for your reply
So how very odd if you run the code I posted above with the latest build it works for you?
I'm not sure how that is possible but in the meantime I will just remove and reapply rather than using setText. Not ideal but not a show stopper
will test this on a iPad3 today...
EDIT: works like a charm ;)
Hey, thanks for the report, glad to hear it :)
I'm just starting to work with text in Corona and the documentation is sparse. Is this API the best way to get retina text these days (universal iPhone/iPad app)? Does it work with the last stable public build or do I need the daily builds?
Thanks!
It sounds like an awesome class. I booked marked this for my next project. Thank you for sharing!
Naomi