Creating a Motion Comic

Hey All, I was wondering if anyone created anything like a motion comic book in codea? I have a png image of a comic book page with say 4 panels. I would like it to skip between each panel after the user touches the screen.

I can get the image in a mesh and display it as a whole, I am having issues selecting what panel to display, moving to the next, and zooming in and out like a motion comic. Sounds like I am having issues with everything. :slight_smile:

The only other way I can think of doing it was breaking each panel of the comic book out separately into a mesh and just call each one as needed. I am just not sure if that would be the best way if I add a full 32 page comic into it. I still struggle with the transition between pannles with some sort of zoom effect.

Anyhoo, Any help or guidance would be greatly appreciated.

@Aalok You can use the sprite command to display the png image. By changing the values in the sprite command, you can select each of the 4 panels and show them as a full screen image.

@Aalok - I suggest you put the images into a table, then it is easy to go forward and back using a counter variable.

For a fade effect, there are several options, the simplest is probably to draw a rectangle over the whole screen, and set the alpha so it starts at 0 (transparent) and gradually increases to 255 (completely hiding the image behind), then you can swap to the next image, and reverse the alpha so it gradually shows the new image. This gives a fade out/fade in effect.

To make the alpha change, all you need is a variable which changes each time you draw. You can make it change as fast or slow as you like.

There are more advanced ways of doing a fade, such as using shaders, if you prefer.

@Aalok Here’s an example of what you could do. Since I don’t have a comic page with the 4 panels, I created a dummy page with 4 color panels. When the program starts, it shows the 4 panels. As you tap the screen, it shows each of the 4 panels as full screen. The program just cycles because I have nothing else to display. You would read the next page after the 4th panel is shown. You could also skip the display of the full page. You could also add code to go backwards.


displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    tab={vec2(0,HEIGHT),vec2(WIDTH,HEIGHT),vec2(0,0),vec2(WIDTH,0)}
    x=0
    y=0
    panel=0    
    img=image(WIDTH,HEIGHT)
    
    -- The following code creates the red, white, green, blue panels.
    -- These panels are for demo only. The image would be replaced 
    -- with the actual comic panels.
    setContext(img)
    rectMode(CORNER)
    spriteMode(CORNER)
    fill(255)
    rect(0,0,WIDTH/2,HEIGHT/2)
    fill(255,0,0)
    rect(WIDTH/2,0,WIDTH/2,HEIGHT/2)
    fill(0,255,0)
    rect(0,HEIGHT/2,WIDTH/2,HEIGHT/2)
    fill(0,0,255)
    rect(WIDTH/2,HEIGHT/2,WIDTH/2,HEIGHT/2)
    setContext()
end
    
function draw()
    background(40,40,50)
    if panel==0 then
        sprite(img,-x,-y,WIDTH)
    else
        sprite(img,-x,-y,WIDTH*2)
    end
end

function touched(t)
    if t.state==BEGAN then
        panel = panel + 1
        if panel>4 then
            panel=0
            x=0
            y=0
            return
        end
        x=tab[panel].x
        y=tab[panel].y
    end
end 

@Aalok, here’s an example using mesh:

I wasn’t sure what you meant by the zoom, but I put in a slide transition. Tap on the left half to go back, right half to forward.

EDIT: Updated code to work with your test image

displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    --comicSections = 3    -- How many sections are in the comic
    local parts = {
                          { 1 }, 
                          { 0.615, 1 },
                          { 1 }
                         }    
    comicImg = createImage("Dropbox:TestComic", parts)    -- Split image into slides 
    currentSection = 1    -- What part are we on
    sectionSize = 1/comicSections    -- How much of the whole image one section takes
    transitionTime = 0.5    -- How long the transitions take
    
    comicMesh = mesh()    -- Set up the mesh
    comicMesh.texture = comicImg
    comicMesh:addRect(WIDTH/2, HEIGHT/2, WIDTH, HEIGHT)
    comicMesh:setRectTex(1, 0, 0, sectionSize, 1)
    
    --autoChange()
end

function autoChange()
    tween.delay(2, function() nextSection() autoChange() end)
end

function createImage(imgKey, tbl)
    comicSections = 0
    for i,v in ipairs(tbl) do
        comicSections = comicSections + #v
    end
    
    local oldImg = readImage(imgKey)
    local newImg = image(WIDTH*2.75, HEIGHT*2.75)
    
    pushStyle()
    setContext(newImg)
    spriteMode(CORNER)
    local rowImg, colImg
    local rowHeight = oldImg.height / #tbl
    local widthInd = 0
    for row,rTbl in ipairs(tbl) do
        
        rowImg = oldImg:copy(0, rowHeight * (#tbl - row), oldImg.width, rowHeight)
        for i,curInd in ipairs(rTbl) do
            prevInd = rTbl[i - 1] or 0
            local width = (rowImg.width * curInd) - (rowImg.width * prevInd)
            colImg = rowImg:copy(rowImg.width * prevInd, 0, width, rowHeight)
            
            sprite(colImg, (newImg.width / comicSections) * widthInd, 0, 
                newImg.width / comicSections, newImg.height)
            
            widthInd = widthInd + 1
        end
    end
    setContext()
    popStyle()
    
    return newImg
end

function nextSection()
    if currentSection < comicSections then
        local time = transitionTime/2
        local next = function()
            local nextSection = currentSection + 1
            local s = 1/comicSections
            tween(time, _G, {sectionSize = s, currentSection = nextSection}, tween.easing.linear)
        end
        
        tween(time, _G, {sectionSize = sectionSize*2}, tween.easing.linear, next)
    elseif currentSection == comicSections then
        currentSection = 1
    end
end

function prevSection()
    if currentSection > 1 then
        local time = transitionTime/2
        local prev = function()
            local s = 1/comicSections
            tween(time, _G, {sectionSize = s}, tween.easing.linear)
        end
        
        local prevSect, s = currentSection - 1, sectionSize*2
        tween(time, _G, {currentSection = prevSect, sectionSize = s}, tween.easing.linear, prev)
    elseif currentSection == 1 then
        currentSection = comicSections
    end
end

function draw()
    background(0)
    comicMesh:setRectTex(1, 1/comicSections * (currentSection-1), 0, sectionSize, 1) -- Focus on sectio
    comicMesh:draw()
end

function touched(touch)
    if touch.state == ENDED and sectionSize == 1/comicSections then
        if touch.x > WIDTH/2 then
            nextSection()
        elseif touch.x < WIDTH/2 then
            prevSection()
        end
    end
end

@Aalok If the 4 panels go across the screen, try this one.


displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    tab={vec2(0,HEIGHT*2),vec2(WIDTH,HEIGHT*2),vec2(WIDTH*2,HEIGHT*2),vec2(WIDTH*3,HEIGHT*2)}
    x=0
    y=0
    panel=0    
    img=image(WIDTH,HEIGHT)
    
    -- The following code creates the red, white, green, blue panels.
    -- These panels are for demo only. The image would be replaced 
    -- with the actual comic panels.
    setContext(img)
    rectMode(CORNER)
    spriteMode(CORNER)
    fill(255)
    rect(0,HEIGHT/2,WIDTH/4,HEIGHT/4)
    fill(255,0,0)
    rect(WIDTH*.25,HEIGHT/2,WIDTH/4,HEIGHT/4)
    fill(0,255,0)
    rect(WIDTH*.5,HEIGHT/2,WIDTH/4,HEIGHT/4)
    fill(0,0,255)
    rect(WIDTH*.75,HEIGHT/2,WIDTH/4,HEIGHT/4)
    setContext()
end
    
function draw()
    background(40,40,50)
    if panel==0 then
        sprite(img,-x,-y,WIDTH)
    else
        sprite(img,-x,-y,WIDTH*4)
    end
end

function touched(t)
    if t.state==BEGAN then
        panel = panel + 1
        if panel>4 then
            panel=0
            x=0
            y=0
            return
        end
        x=tab[panel].x
        y=tab[panel].y
    end
end 

@dave1707 Ty for the code, it worked nicely, the only tweaks I needed to make was that each panel was not the same size in the exampe.png I tried. Thanks again for your help.

@JakAttak

Thanks for your help as well, I really want to test your code a bit more. I am running into an issue trying to add my example sprite into your mesh. I may be confused on where to call it. I can get the sprite() to call once but not through the loop.

Here is a link to the png file that I am using to test. I just grabbed it from the web, I do not claim at all to own the rights to it :slight_smile:

https://www.dropbox.com/s/fgqnpgtvfz73wvt/ComicPage_1.png

Also, if you look at the ComicPage_1.png, I forgot to mention in my OP that like many comic books, the panels my not always be the same size. I would have to set the display size to match each panel.

I really do appreciate all of the help and this is a good learning experience.

@Aalok, I went and changed to code to work with your image. Updated code is above in my old post. You could now change it to work with any image. Here’s how: There is the table ‘parts’ and your image. Parts is what tells the code how to split your image correctly. So it’s a table that has tables in it. Those tables represent rows, so in the example above, there are three. In those tables you put a number that is where each image in that row ends. So in row 1 there is only one image that ends at 1 (100% of the image width). Same in row 3. However, in row 2 there are two images. The first takes about 60% of the row width, so it ends at about .6 and the second one takes an additional 40% so it ends at 1. (.6 + .4 = 1). I hope I was clear, if you need anymore help feel free to ask.

parts = {
               -- First row in the image
               { 1 --[[ Only one image, it takes 100% of that row's width--]] },
               -- Second row in image
               { 0.615, 1 --[[ Two images in row, split about 60/40--]] },
               -- Third row in the image
               { 1 --[[ Same as first row--]] }
             }

To try and be more clear, here are some other images (Not mine, credit to explosm.net) and their corresponding parts tables.

  1. https://www.dropbox.com/s/ue4v86ys4f2gl3b/Comic%201.png

    parts = {
    {0.33, 0.66, 0.99}
    }

  2. https://www.dropbox.com/s/1h57yno324yz8ri/Comic2.png

    parts = {
    {0.5, 1},
    {0.5, 1}
    }

  3. https://www.dropbox.com/s/s0ms20m7ei44k8w/Comic8.png

    parts = {
    {0.33, 0.66, 1},
    {0.33, 0.66, 1}
    }

@JakAttak This code is amazing. Thanks so much for your time and help. I was messing around with it to try 2 things.

I wanted to display the entire .png at first then cycle through the ‘parts’. I tried adding the image to the table without the parts and I also messed with currentPanel= 0.

Nothing really worked without messing up the ‘parts’ portion for the remainder of the image.

The only other question I have is, how is the best way to add more then one comic book page to this code? Say I have a different .png file for each page of a comic book. I will need to define the image and parts for each page. I am not sure how to add another .png file without breaking the first image.

Any thought on the above questions would be greatly appreciated.

@Aalok I know @juaxix has comics in his 6Dimensions app. It uses Tweens for translating/ moving the camera.

Also, slightly off topic, the Penguins of Madagascar was one of my favorite shows before they slowed down and stopped making it.

@Aalok, I have actually been expanding on this for fun and I have a new version where you can add as many comics as you wish. Give me a minute and I’ll post it. The main idea is, I turned the above code into a class and made a new table of comics. then i can update the class with the comic i want displayed.

@Aalok : Here you go:
P.S. Check out the Stuff Tab and edit the COMICSINFO table to include the correct info or it won’t work

--# Main
 
--Comic Book Single Install
--Installer created by @Briarfox
--- This will pull the Comic Book project into Codea for you
-- Instructions:
-- * Create a new project in Codea named Comic Book If you chose another name please change the variable Below
--This is case sensitive
ProjectName = "Comic Book"
-- * Paste this into the Main (not from the raw view, as iSafari will escape special characters)
-- * Make sure there is a single tab in the project
-- * Run and wait for success!
-- If all went well, you should have a Comic Book project now
 
 
function setup()
    local jsonCode
    getJsonLib()
end
 
function getJsonLib()
    local tabs = listProjectTabs()
    if #tabs == 1 then
        print("Attempting to load json...")
        local handleSuccess = function(data)
            --saveProjectTab("json", data)
            jsonCode = data
            --sound(SOUND_POWERUP, 42179)
            print("json code loaded...")
            if jsonCode then
                print("Attempting to pull project...")
                l = loadstring(jsonCode)
                l()
               GetProject() 
            end
        end
        http.request("https://dl.dropboxusercontent.com/s/9e4nvqeu4hsux2q/Json.lua?token_hash=AAFyMB98j4bnt_1gawf9wSke52hsoC7hsIvARcTuZNeOEw&dl=1", handleSuccess)
        end
end
 
function GetProject()
    local projectCheck = listProjectTabs(ProjectName)
    if #projectCheck ~= 0 then
   local handleSuccess = function(data,i,j)
        local gist = json.decode(data)
        local projName = ProjectName
        if gist.files["1aTabOrder"] then
            print("***Tab Order Found***")
            local taborder = gist.files["1aTabOrder"].content
            local strStart =1
            local strEnd =0
            strStart = string.find(taborder,"#",strEnd)
            strEnd = string.find(taborder,"\
",strStart)
            while strStart do
                local tmp = string.sub(taborder,strStart+1,strEnd-1)
                local name = ProjectName..":"..tmp
                tmp = tmp..".lua"
                saveProjectTab(name,gist.files[tmp].content)
                strStart = string.find(taborder,"#",strEnd)
                strEnd = string.find(taborder,"\
",strStart)
            
            end    
        else
            for k,v in pairs(gist.files) do
                local name = ProjectName .. ":" .. string.gsub(k,".lua","")
                saveProjectTab(name, v.content)
            end
        end
        sound(SOUND_PICKUP, 11797)
        print("Success!")
    end
    local handleFailure = function(data)
        sound(SOUND_EXPLODE, 32351)
        print(data)
        
    end 
    http.request("https://api.github.com/gists/11052f228a9b8603c844",handleSuccess, handleFailure)
    else
        output.clear()
            sound(SOUND_EXPLODE, 32351)
            print([[ERROR
Project name incorrect! 
Please make sure the variable ProjectName = "your project" matches the project name.
This is case sensitive!]])
        end
end

@JakAttk - This code is sweet and way more then I expected. I really appreciate all you have done. I hope it comes useful to others as they come across this thread in the future.

I was able to add my images to the “stuff” tab and modify the parts under “COMICSINFO”

I haven’t ran into a single issue.

Thanks again for all of your help.

@Aalok no problem. I’d love to see what you do with it when you’re done!

@JakAttak - Yeah sure, I will be sure to shoot you some updates as I go along.

I do have another question, go figure :).

I am adding the “comic book” project as a dependency in another game project. It is a RPG. Throughout the game there would be “events” that would trigger a comic book page. I want to use the comics many times throughout the game for storytelling purposes.

So, to test this, I was trying to modify the Main tab of your project to change the GAMESTATE to “viewer” and then list out the comic.img and comic.parts that I wanted. That way I didn’t have to display the chooser each time.

When I made the changes, I would get the chooser still but then none of the comics would load. Seems like it was stuck in a loop.

Last question… I mean it this time. Say I have a comic strip that I want to display all together that runs over more then on .png file, is there a way to do that with the code as it currently is written? Some of the work I am on is up to 4 or 5 pages that need to be shown all together.

Thanks again for everything.

Showing the comics without the chooser shouldn’t be too hard, simply change the start GameState and then pass in the comic you want before switching to the viewer.

And as for multiple pngs as one, not possible as it is currently written, but you could combine them into one image

Thanks JakAttk. The idea of adding all comic pages as a single .png file worked flawlessly. Thanks for the advice.

I been testing bypassing the chooser screen and as best as I can do is get a blank page to display with 0 of 0 in the panel counter. So, it appears to be running the viewer but I’m not passing the correct comic info info to display.

Can you look at my below changes that I made to the setup() and let me know if I am getting close? I thought I had to leave the gamestate as chooser to setup the image, then I added my COMICINFO followed by changing the game state to viewer.

I also moved these changes outside of setup above then below the changestate function. That just sent me to the chooser.

-- Comic Book
 
displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)
 
VERSION = "1.0"
PROJECTNAME = "Comic Book"
DESCRIPTION = "Choose From and View Comics"
 
function setup()
    GAMESTATES = {}
    GAMESTATES["viewer"] = ComicViewer()
    GAMESTATES["chooser"] = ComicChooser(COMICSINFO)
    
    GAMESTATE = "chooser"
    COMICSINFO = {
                { name = "Invisible Ink", img = readImage("Dropbox:ComicPage_1"), 
                  parts = {{1}, {0.33,1}, {1}} },
                }
                
    GAMESTATE = "viewer"
    
    ag = AutoGist(PROJECTNAME, DESCRIPTION, VERSION, true)
    ag:backup()
end

 
function changeState(s)
    collectgarbage()
    if GAMESTATES[s] ~= nil then
        GAMESTATE = s
    end
end
 
function draw()
    background(0)
    font(STANDARDFONT)
    
    if GAMESTATES[GAMESTATE] ~= nil then
        GAMESTATES[GAMESTATE]:draw()
    end
end
 
function touched(touch)
    if GAMESTATES[GAMESTATE] ~= nil then
        GAMESTATES[GAMESTATE]:touched(touch)
    end
end

@Aalok, you don’t need the chooser at all

Try this:

    local comic = COMICSINFO[1]     -- Comic you want to view
    GAMESTATES["viewer"]:loadComic(comic.img, comic.parts)       -- Load the comic you chose
    changeState("viewer")      -- Switch to the viewer to see it

P.S. If you take out the chooser, you should change the code in ComicViewer nextSection() and prevSection() where it goes back to the chooser to go back to where you want it to go.