I suspect that display.newText() is the issue because it has special handling on WP8. That and the OmNomster app which uses storyboard and which doesn’t use display.newText() anywhere (completely uses bitmap fonts) doesn’t have this problem. So, that’s our working theory as part of the cause.
I Can also second that a lot of problems are caused by text updates while doing scene changes or resuming from suspend mode (IE after a full screen ad or while loading several banner ads)
If you were to put a delay on any screen changes, removing etc, resuming app suspend or anything or make the text update first just as long its not the same time., even 2 seconds gap then see if the problem still happens?
I’m not so sure it is related to .newText() because my app uses pure bitmap fonts and the sample project I made that exhibits the glitch also does not use .newText()
Are you still using widget buttons? Because those use display.newText() internally. Even if you don’t set a label. (Remember I made an optimization for you for label-less buttons.)
I’m not using corona widget buttons either. I made my own buttons that use bitmap font text.
I don’t suppose this is happening after a full screen admob is it?
Nope. It happens whether ads are in the app or not.
Based on my current findings, this issue happens when displaying a static screen (no animation or movement) after removing an image and then display a new image object or after updating a text object. It’s a bit more complicated than that, but it’s much more likely to happen on static screens.
I believe I have a fix for this now, but I’ll have to run it through a gauntlet of tests tomorrow to make sure it works and doesn’t break anything else. If all goes well, we’ll make this fix available in a daily build by Monday (but hopefully sooner). Thanks for your patience.
Maybe that is connected to the problems I was having as well as I had hid a menu screen background while unhiding some buttons, all with alpha pngs, which revealed the normal background underneath. I also had text updating but perhaps this threw a spanner in the works.
Everyone,
A fix to this issue will be made available in daily build #2484 in the next couple of days here…
Awesome! Just curious, what was the issue?
This issue only happens when suspending/resuming on static screens (no animations; no movement) and only after you’ve updated a text object or removed an image object and display a new one. It’s a little more complicated than that, but that’s typically the cause of it. That’s also why you won’t see this issue during gameplay.
Anyways, if you can quickly confirm it on your end when the next daily build comes out, then that would be great!
Awesome work. Thanks Joshua!
Congratulations!
I just downloaded it and played it now. Very nice app! And I hope you had a good experience putting together on WP8.
By the way, I play a similar anagram style game on my iPhone. It’s actually my most played game for the past several years. I like puzzle/word games.
Congrats Spacewolf. We shared the game on Facebook and Bobby Ghari asked if you used a bitmap font library for the timer display.
If you don’t mind giving him a few pointers, you can share your experience here, https://www.facebook.com/CoronaGeek/posts/736019049778967?comment_id=736079253106280&offset=0&total_comments=1
I have the game on my Nokia and it plays great. As soon as I get the device registered with the app store, I’ll leave a review.
Thanks in advance.
Thanks guys and thanks for the post! I don’t mind at all sharing my experience. I’ll reply as soon as I can
Downloaded, works great!! great job.
One issue I found - Run the app, go to the menu but dont start any rounds yet.
Hit the home button on your phone ( app should pause ) navigate somewhere else and then hit the back but on the phone until you get back to your app. the fonts were all distorted on my phone. then when I started the round to play. They really got distorted.
NOw if it works on your testing then dont worry about it. My phone is pretty flaky, I’ll test on my wife windows 8 phone when she gets back home and if you hear nothing else from me then all is well. But i just wanted to point that our to you.
I’m leaving a review for your app
Larry
@doubleslashdesign Oh wow you are totally right. I’ve never seen that happen before… If you do that on the main menu screen the background goes black and the title graphic disappears (this is an actual image not the bitmap font).
I think this has something to do with the bitmap font spritesheets because those images are white characters saved on a black background. As you navigate through the menus to the game, the background actually becomes the bitmap font spritesheet! lol Would anyone be able to point out what is wrong with the bitmap font code? I took it from the community but it does use the old sprite.lua file from graphics 1.0 so maybe that is causing this issue…
bmf.lua
module( ..., package.seeall ) -- AngelCode bitmap font support -- Updated for Graphics 2.0 -- Download sprite module from https://github.com/coronalabs/framework-sprite-legacy/raw/master/sprite.lua local sprite = require( "libraries.bmf.sprite" ) -- Specify an Angelcode format bitmap font definition file (".FNT") -- The spritesheet(s) that this file references need to be located in the resource directory. -- Return value is a font object that can be used when calling newString function loadFont( fntFile, path ) local function extract( s, p ) return string.match( s, p ), string.gsub( s, p, '', 1 ) end local path = path or "" local font = { info = {}, spritesheets = {}, sprites = {}, chars = {}, kernings = {}, size = 0 } local readline = io.lines( system.pathForFile( path .. fntFile, system.ResourceDirectory ) ) for line in readline do local t = {}; local tag; tag, line = extract( line, '^%s\*([%a\_]+)%s\*' ) while string.len( line ) \> 0 do local k, v k, line = extract( line, '^([%a\_]+)=' ) if not k then break end v, line = extract( line, '^"([^"]\*)"%s\*' ) if not v then v, line = extract( line, '^([^%s]\*)%s\*' ) end if not v then break end t[k] = v end if tag == 'info' or tag == 'common' then for k, v in pairs( t ) do font.info[k] = v end elseif tag == 'page' then font.spritesheets[1 + t.id] = { file = t.file, frames = {} } elseif tag == 'char' then t.letter = string.char( t.id ) font.chars[t.letter] = {} for k, v in pairs( t ) do font.chars[t.letter][k] = v end if 0 + font.chars[t.letter].width \> 0 and 0 + font.chars[t.letter].height \> 0 then font.spritesheets[1 + t.page].frames[#font.spritesheets[ 1 + t.page].frames + 1 ] = { --textureRect = { x = 0 + t.x, y = 0 + t.y, width = -1 + t.width, height = -1 + t.height }, --CLF removed the -1, it was causing issues with fonts with borders textureRect = { x = 0 + t.x, y = 0 + t.y, width = t.width, height = t.height }, spriteSourceSize = { width = 0 + t.width, height = 0 + t.height }, spriteColorRect = { x = 0, y = 0, width = -1 + t.width, height = -1 + t.height }, spriteTrimmed = true } font.sprites[t.letter] = { spritesheet = 1 + t.page, frame = #font.spritesheets[1 + t.page].frames } end elseif( tag == 'kerning' ) then font.kernings[string.char( t.first ) .. string.char( t.second )] = 0 + t.amount end end for k, v in pairs( font.spritesheets ) do font.spritesheets[k].sheet = sprite.newSpriteSheetFromData( path..v.file, v ) end for k, v in pairs( font.sprites ) do font.sprites[k] = sprite.newSpriteSet( font.spritesheets[v.spritesheet].sheet, v.frame, 1 ) end return font end -- extend an object with accessor behaviors local function accessorize( t ) local mt = getmetatable( t ) setmetatable( t, { \_\_index = function( t, k ) if rawget( t, 'get\_'..k ) then return rawget(t, 'get\_'..k )( t, k ) elseif rawget( t, 'raw\_'..k ) then return rawget( t, 'raw\_'..k ) elseif mt.\_\_index then return mt.\_\_index( t, k ) else return nil end end, \_\_newindex = function( t, k, v ) if rawget( t, 'set\_'..k ) then rawget( t, 'set\_'..k )( t, k, v ) elseif rawget( t, 'raw\_'..k ) then rawset( t, 'raw\_'..k, v ) elseif mt.\_\_newindex then mt.\_\_newindex( t, k, v ) else rawset( t, 'raw\_'..k, v ) end end, } ) end -- extend an object with cascading removeSelf local function removerize( t ) local old = t.removeSelf t.removeSelf = function( o ) for i = o.numChildren, 1, -1 do o[i]:removeSelf() end old( o ) end end -- Pass a font object (obtained from loadFont) and a string to render -- Return value is a DisplayObject of the rendered string -- object.font can be read/modifed -- object.text can be read/modified -- object.align can be read/modified - left/right/center (multiline not yet fully supported for non-left) -- object.input( function(text), { filter = function(), max = 32 } ) -- turns the object into a text input. -- the callback is hit when the user presses "return" or the field losed focus. -- this code is under development - more documentation will be added soon... function newString( font, text, newsize) --local finalScale = 100/origsize \*newsize/100; local finalScale = 100/font.info.size \*newsize/100; local obj = display.newGroup() accessorize( obj ) removerize( obj ) obj.set\_font = function( t, k, v ) obj.raw\_font = v if t.text then t.text = t.text end end -- Set the alignment of the object obj.set\_align = function( t, k, v ) local w = t.textWidth if t.raw\_align == 'right' then for i = 1, t.numChildren do t[i].x = t[i].x + w end elseif t.raw\_align == 'center' then for i = 1, t.numChildren do t[i].x = t[i].x + math.floor( w \* 0.5 ) end end t.raw\_align = v if t.raw\_align == 'right' then for i = 1, t.numChildren do t[i].x = t[i].x - w end elseif t.raw\_align == 'center' then for i = 1, t.numChildren do t[i].x = t[i].x - math.floor( w \* 0.5 ) end elseif t.raw\_align ~= 'left' then t.raw\_align = 'left' end end -- Set the text for the object obj.set\_text = function( t, k, v ) if ( v == t.raw\_text ) then return end t.raw\_text = v for i = t.numChildren, 1, -1 do t[i]:removeSelf() end local oldAlign = ( t.align or 'left' ) local oldTint = ( t.tint or {0, 0, 0, 0} ) t.align = 'left' local x = 0; local y = 0 local last = ''; local xMax = 0; local yMax = 0 if t.raw\_font then for c in string.gmatch( t.raw\_text..'\n', '(.)' ) do if c == '\n' then x = 0; y = y + t.raw\_font.info.lineHeight if y \>= yMax then yMax = y end elseif t.raw\_font.chars[c] then local rfc = t.raw\_font.chars[c] if 0 + t.raw\_font.chars[c].width \> 0 and 0 + t.raw\_font.chars[c].height \> 0 then local letter = sprite.newSprite( t.raw\_font.sprites[c] ) if t.raw\_font.kernings[last .. c] then x = x + font.kernings[last .. c] end t:insert( letter ) letter.anchorX = 0 letter.anchorY = 0 letter.x = t.raw\_font.chars[c].xoffset + x letter.y = t.raw\_font.chars[c].yoffset - t.raw\_font.info.base + y last = c end --x = x + t.raw\_font.chars[c].xadvance x = x + t.raw\_font.chars[c].xadvance + (t.raw\_font.info.outline or 0) --CLF added support for outlines if x \>= xMax then xMax = x end end end obj.textWidth = xMax --[[local background = display.newRect( 0, -t.raw\_font.info.base, xMax, yMax ) background.isBackground = true --CLF Added to support tinting of font. obj:insert( background ) obj.background = background background:setFillColor( 0, 0, 0, 0 ) --]] end t.align = oldAlign t.tint = oldTint end -- set the tint of the object obj.set\_tint = function( t, k, v ) t.raw\_tint = v for i = t.numChildren, 1, -1 do if v[4] then t[i]:setFillColor(v[1],v[2],v[3],v[4]) else t[i]:setFillColor(v[1],v[2],v[3]) end end --if t.text then t.text = t.text end end obj.input = function( f, args ) -- spawn the text field invisibly local field -- Handle character insertion/deletion local function char() -- check if any character has been added or deleted if field.text ~= '--' then if string.len( field.text ) \< 2 then -- backspace was pressed if string.len( obj.text ) \> 0 then obj.text = string.sub( obj.text, 1, -2 ) end else -- some other key was pressed obj.text = obj.text..string.sub( field.text, 3 ) end field.text = '--' if args.filter then obj.text = string.sub( args.filter( obj.text ), 1, (args.max or 32) ) else obj.text = string.sub( obj.text, 1, (args.max or 32) ) end end end Runtime:addEventListener( 'enterFrame', char ) -- Handle the "done" phase local function done( e ) if e.phase == 'submitted' or e.phase == 'ended' then native.setKeyboardFocus( nil ) field:removeSelf() Runtime:removeEventListener( 'enterFrame', char ) f( text ) end end field = native.newTextField( 0, 0, 240, 24, done ) field.text = '--' field.isVisible = false native.setKeyboardFocus( field ) end obj.font = font obj.align = 'left' obj.text = (text or '') obj.xScale = finalScale; obj.yScale = finalScale; return obj end function newParagraph(font, text, fontSize, width) local obj = display.newGroup() accessorize( obj ) removerize( obj ) obj.set\_font = function( t, k, v ) obj.raw\_font = v --if t.text then t.text = t.text end end obj.set\_paragraphWidth = function( t, k, v ) obj.raw\_paragraphWidth = v --if t.text then t.text = t.text end end obj.set\_tint = function( t, k, v ) obj.raw\_tint = v for i = t.numChildren, 1, -1 do for j = t[i].numChildren, 1, -1 do if(not t[i][j].isBackground) then if v[4] then t[i][j]:setFillColor(v[1],v[2],v[3],v[4]) else t[i][j]:setFillColor(v[1],v[2],v[3]) end end end end --if t.text then t.text = t.text end end obj.set\_text = function(t, k, v) --save the new raw text t.raw\_text = v --remove all children for i = t.numChildren, 1, -1 do t[i]:removeSelf() end --determine the width of a space local space = newString(font, " ", fontSize) local spaceWidth = space.raw\_font.info.outline\*-1 or 0 space:removeSelf() space = nil --Create our word-wrapped paragraph. local spaceLeft = t.raw\_paragraphWidth local x, y = obj.x, obj.y for word, spacer in string.gmatch(v, "([^%s%-]+)([%s%-]\*)") do local w = newString(font, word..spacer, fontSize) if(w.width + spaceWidth) \> spaceLeft then spaceLeft = t.raw\_paragraphWidth - w.width - spaceWidth y = y + w.raw\_font.info.lineHeight x = obj.x w.x = x else w.x = x + t.raw\_paragraphWidth - spaceLeft spaceLeft = spaceLeft - w.width - spaceWidth end w.y = y obj:insert(w) end end obj.paragraphWidth = width or display.viewableContentWidth obj.text = text obj.font = font obj.tint = {255,255,255} return obj end
sprite.lua:
local Library = require "CoronaLibrary" local sprite = Library:new{ name='sprite', publisherId='com.' } local function toImageSheet( spriteSet ) assert( spriteSet.spriteSheet, "toImageSheet(): Bad spriteSet parameter: spriteSet.spriteSheet is nil" ) local options local spriteSheet = spriteSet.spriteSheet if spriteSheet.format == "simple" then local w = spriteSheet.width local h = spriteSheet.height options = { width = w, height = h, numFrames = spriteSet.numFrames, } elseif spriteSheet.format == "complex" then assert( spriteSheet.spriteSheetFrames, "spriteSheet.spriteSheetFrames is nil") options = { spriteSheetFrames = spriteSheet.spriteSheetFrames } end local filename = spriteSheet.filename local baseDir = spriteSheet.baseDir if baseDir then return graphics.newImageSheet( filename, baseDir, options ) else return graphics.newImageSheet( filename, options ) end end local function toSequenceData( spriteSet ) assert( spriteSet.spriteSheet, "toSequenceData(): Bad spriteSet parameter: spriteSet.spriteSheet is nil" ) local spriteSheet = spriteSet.spriteSheet local default = { name = "default", start = spriteSet.startFrame, count = spriteSet.numFrames, } local sequenceData = spriteSet.sequences or {} table.insert( sequenceData, default ) return sequenceData end sprite.newSprite = function( spriteSet ) assert( spriteSet, "spriteSet is nil", "sprite.newSprite(): spriteSet is nil." ) local s if spriteSet.type == "newSpriteArgs" then assert( type( spriteSet.args ) == "table", "sprite.newSprite(): 'spriteSet.args' is nil." ) s = display.newSprite( unpack( spriteSet.args ) ) else assert( spriteSet.type == "spriteSet" or spriteSet.type == "spriteMultiSet", "sprite.newSprite(): incorrect 'spriteSet.type'." ) local imageSheet = toImageSheet( spriteSet ) local sequenceData = toSequenceData( spriteSet ) assert( sequenceData, "sprite.newSprite(): failed to create sequenceData" ) s = display.newSprite( imageSheet, sequenceData ) end function s:prepare( name ) s:setSequence( name ) end return s end -- sprite.add( spriteSet, sequenceName, startFrame, frameCount, time, [loopParam] ) sprite.add = function( spriteSet, sequenceName, startFrame, frameCount, time, loopParam ) assert( spriteSet, "spriteSet is nil", "sprite.addSprite(): spriteSet is nil." ) assert( spriteSet.type == "spriteSet", "sprite.addSprite(): incorrect 'spriteSet.type'.") local sequences = spriteSet.sequences if not sequences then sequences = {} spriteSet.sequences = sequences end table.insert( sequences, item ) local loopCount = 0 local loopDirection if loopParam and 0 ~= loopParam then loopCount = 1 if -1 == loopParam then loopDirection = "bounce" elseif -2 == loopParam then loopCount = 0 loopDirection = "bounce" end end local item = { name = sequenceName, start = startFrame, count = frameCount, time = time, loopCount = loopCount, loopDirection = loopDirection, } table.insert( sequences, item ) end local function spriteSheetToImageSheet( spriteSheet ) assert( false, "spriteSheetToImageSheet() is not implemented." ) return nil end -- sprite.newSpriteMultiSet() sprite.newSpriteMultiSet = function( sequences ) assert( #sequences \> 0, "sprite.newSpriteMultiSet(): sequences array is empty." ) -- Assume first element defines default sheet local defaultSheet = spriteSheetToImageSheet( sequences[1].sheet ) assert( defaultSheet, "sprite.newSpriteMultiSet(): no sheet param." ) local sequenceData = {} for i=1,#sequences do local seq = sequences[i] local imageSheet = spriteSheetToImageSheet( seq.sheet ) local data = { sheet = imageSheet, frames = seq.frames, } table.insert( sequenceData, data ) end local result = { type = "newSpriteArgs", args = { defaultSheet, sequenceData, } } return sequences end -- sprite.newSpriteSet( spriteSheet, startFrame, frameCount ) sprite.newSpriteSet = function( spriteSheet, startFrame, frameCount ) assert( spriteSheet, "sprite.newSpriteSet(): spriteSheet is nil." ) assert( spriteSheet.type == "spriteSheet", "sprite.newSpriteSet(): spriteSheet is malformed." ) local result = { type = "spriteSet", spriteSheet = spriteSheet, numFrames = frameCount, startFrame=startFrame, } return result end -- sprite.newSpriteSheet( spriteSheetFile, [baseDir,] frameWidth, frameHeight ) sprite.newSpriteSheet = function( ... ) local args = { ... } local spriteSheetFile = args[1] local nextArg = 2 local baseDir = args[nextArg] if type(baseDir) ~= "userdata" then baseDir = nil else nextArg = nextArg + 1 end local frameWidth, frameHeight = args[nextArg], args[nextArg+1] local result = { type = "spriteSheet", format = "simple", width = frameWidth, height = frameHeight, filename = spriteSheetFile, baseDir = baseDir, } function result:dispose() -- no-op end return result end -- sprite.newSpriteSheetFromData( spriteSheetFile, [baseDir,] coordinateData ) sprite.newSpriteSheetFromData = function( ... ) local args = { ... } local spriteSheetFile = args[1] local nextArg = 2 local baseDir = args[nextArg] if type(baseDir) ~= "userdata" then baseDir = nil else nextArg = nextArg + 1 end local coordinateData = args[nextArg] local result = { type = "spriteSheet", format = "complex", spriteSheetFrames = coordinateData.frames, filename = spriteSheetFile, baseDir = baseDir, } function result:dispose() -- no-op end return result end return sprite
Thanks!