Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Ghosties - work in progress

edited April 2015 in Examples Posts: 730

Here is a simple example of a finite state machine engine - mainly aimed at beginners with a decent amount of comments. This came from a request / comment on one of the videos of the fruit ninja ghost game I'm working on -

Hopefully the commenter is lurking here and finds it useful

--Simple Finite State Machine plus rising and fading sprite menu effect
-- by West

function setup()
    displayMode(FULLSCREEN)
    --set up the states for the Finite State Machine.
    READY=1  --the state for the main menu
    PLAYING=2 --the state for the actual game play
    LEVELCOMPLETE=3 --the state for the level complete screen
    GAMEOVER=4 --the state for the game over screen
    gamestate=READY --set a variable to control the current state

    -- set up a variable to allow a short delay between moving from different states
    scrdelay=0
    --set up a variable to store the current level
    currentlevel=1

    --set up a table to handle the touches
    touches={}


    spacemen={} --set up a table to hold each of the spacemen objects
    --create a loop to add 30 new instances of the spaceman to the table
    for i=1,30 do
        -- insert a new instance with the following variables
        -- x - the horizontal screen position of the spaceman.
        -- y - the vertical screen position of the spaceman.
        -- size - the size of the spaceman - initial value a random value between 0.3 and 1
        -- fade - the transparency of the spaceman
        -- dir - the direction of the fade with 1 meaning getting more solid and -1 getting more transparent
        -- spd - the speed at which the spaceman rises up the screen
        table.insert(spacemen,{x=math.random(WIDTH),y=math.random(HEIGHT),size=(2+math.random(8))/10,fade=math.random(255),dir=1,spd=math.random(10)})
    end
end

-- This function gets called once every frame. This is the main program loop
function draw()
    --the controller for the Finite State Machine. All this does is check to see what state the game is currently in and calls the appropriate function for that state
    -- the scrdelay variable is a counter which is used to add a delay between moving between screens
    if gamestate==PLAYING then
        play()
    elseif gamestate==READY then
        scrdelay = scrdelay + 1
        menu()
    elseif gamestate==LEVELCOMPLETE then
        scrdelay = scrdelay + 1
        levelover()
    elseif gamestate==GAMEOVER then
        scrdelay = scrdelay + 1
        gameoverscreen()
    end
end

-- the function to drawthe main menu screen. Consists of 3 parts:
-- 1) the rising and fading spacemen
-- 2) the title
-- 3) the "tap to start" message which appears after the delay
-- handling the user interaction through touches is done through the touch function and is the same regardless of which gamestate we are in
function menu()
    --set a background colour
    background(33, 71, 148, 255)
    -- 1) the rising and fading spacemen
    --set up a loop where each instance of the spacemen will be dealt with in turn
    for i,s in pairs(spacemen) do
        --inside this loop, the current spaceman is represented by the variable s and each of the properties is accessed using the .property form
        --For example, to get the x position of the current spaceman use s.x

        -- set the transparency of the spaceman based on its fade value
        tint(255,255,255,s.fade)
        -- draw the spaceman on the screen based on its position and size. 65 and 92 are the width and height of the spaceman spriteand these vales are multiplied by the scaling factor (or size) variable
        sprite("Platformer Art:Guy Standing",s.x,s.y,65*s.size,92*s.size)
        --change the transparency based on the dir property which can be either 1 or -1
        s.fade = s.fade + s.dir
        --run a check on the fade - if it is outside the maximum value (255 / solid) or minimum value (0 / fully transparent) then reverse the direction of fade
        -- so if dir was 1 then the spaceman is getting more solid by adding 1 to the fade property each loop of the main game
        -- When it reaches the maximum value of 255 then set dir to be -1 and the fade value will now start to decraese and the spaceman will fade
        if s.fade>255 or s.fade<1 then s.dir = s.dir * -1 end
        --[[
        --optional extra - move the spaceman to a new random position when it fades away completely
        if s.fade<1 then
        s.x=math.random(WIDTH)
        s.y=math.random(HEIGHT)
    end
        ]]--
        --move the spaceman up the screen based on its speed. Here the raw speed has been slowed by a factor of 10
        s.y = s.y + s.spd/10
        --detect if the spaceman has gone past the top of the screen and if so then reset its height to the bottom of the screen
        -- the + and - offsets are to take into account the height of the sprite.  the y position is the centre of the sprite so there would be an untidy jump as the sprite would disappear when the middle of the sprite crosses the top of the screen
        if s.y>HEIGHT+50 then s.y=-50 end
    end
    --housekeeping - reset the transparency to ensure that anything else drawn isn't faded
    noTint()
    -- 2) the title
    --set up the font and print out the title
    strokeWidth(2)
    fill(171, 199, 221, 255)
    font("AmericanTypewriter-Light")
    fontSize(96)
    text("FSM Demo",WIDTH/2,5*HEIGHT/8)

    -- 3) the "tap to start" message which appears after the delay
    font("AmericanTypewriter-Light")
    fontSize(32)
    if scrdelay>100 then
        text("Tap to start",WIDTH/2,3*HEIGHT/8)
    end
end

-- these next two functions are basically repeats of points 2) and 3) from the menu function above
function levelover()
    background(33, 71, 148, 255)
    strokeWidth(2)
    fill(141, 127, 33, 255)
    fontSize(64)
    --  use .. to join strings together - here the current level is inserted into the message
    text("Level "..currentlevel.." complete",WIDTH/2,HEIGHT/2)
    fontSize(32)
    if scrdelay>100 then
        text("Tap for next level",WIDTH/2,3*HEIGHT/8)
    end
end

function gameoverscreen()
    background(33, 71, 148, 255)
    strokeWidth(2)
    fill(141, 127, 33, 255)
    fontSize(64)
    text("Game over",WIDTH/2,HEIGHT/2)
    fontSize(32)
    if scrdelay>100 then
        text("Tap for menu",WIDTH/2,3*HEIGHT/8)
    end
end


function play()
    --very simple game - ifyou tap on the left hand side of the screen you win
    -- this is just to demonstrate the finite state machine - obviously you would substitue your actual game function here
    background(198, 18, 18, 255)
    fill(117, 255, 0, 255)
    rect(0,0,WIDTH/2,HEIGHT)
    fill(63, 28, 144, 255)
    font("AmericanTypewriter-CondensedLight")
    fontSize(72)
    text("The Game",WIDTH/2,3*HEIGHT/4)
    text("Win",WIDTH/4,HEIGHT/2)
    text("Lose",3*WIDTH/4,HEIGHT/2)
end


function touched(touch)
    --this function is called each time the screen is touched regardless of what gamestate we are in.
    --therefore we need to do a check on which state we are in and respond accordingly
    -- in the menu, level complete and game over screens we are simply responding to a tap anywhere
    -- in the actual gameplay we need to check where in the screen the tap has occured so a separate function is needed
    if touch.state==ENDED or touch.state==CANCELLED then
        if gamestate==PLAYING then
            --separate function to check position of tap
            processTouch(touch)
        elseif gamestate==READY and scrdelay>100 then
            --if the gamestate is on the main menu, change it to the game play state and reset the scrdelay counter
            gamestate=PLAYING
            scrdelay=0
        elseif gamestate==LEVELCOMPLETE and scrdelay>100 then
            --if the gamestate is on the level complete, change it to the game play state and reset the scrdelay counter
            gamestate=PLAYING
            scrdelay=0
            --    increase the current level
            currentlevel = currentlevel + 1
        elseif gamestate==GAMEOVER and scrdelay>100 then
            --if the gamestate is on the gameover, change it to the main menu state and reset the scrdelay counter
            gamestate=READY
            --reset the current level
            currentlevel=1
        end
        touches[touch.id] = nil
    else
        touches[touch.id] = touch
    end
end

function processTouch(touch)
    --check the x position of the tap - if it is greater than half the width then change the game state to gameover and reset the scrdelay counter
    if touch.x>WIDTH/2 then
        scrdelay=0
        gamestate=GAMEOVER
    else
        --otherwise the tap was on the left and so change the game state to level complete and reset the scrdelay counter
        scrdelay=0
        gamestate=LEVELCOMPLETE
    end
end

Comments

  • Posts: 2,020

    I love the slicing animation here, very impressive. One suggestion for the state machine, use a function pointer instead of a set of globals and a branching if statement. So to change state you write scene=play, scene=menu, scene=levelover etc, and then your draw loop is just scene().

  • Posts: 289

    it felt so right, ghosts are so cute that i could slice them

  • Posts: 730

    @yojimbo - I've not really tried that before but it looks a much more elegant solution. This was supposed to be pitche'd at an absolute beginner so don't know if that adds a complexity which is harder to understand (as opposed to if statements which are pretty close to "English")

    Have also done some new backgrounds for the ghost slicing game. 10 bonus points for the subtle movie reference in one background

  • Posts: 300

    Batteries not included. ;)

  • Posts: 2,020

    Ghostbusters!

  • Posts: 2,020

    I love the silhouette art style. Are you using an iPad vector art app like Inkpad or something for that?

    I also really like the idea of the different methods by which each ghost has to be dispatched. I always found the original Fruit Ninja mechanic to be a little simplistic (even my cat can play it), but this adds a very original twist (it's probably too difficult for my cat now tho ;-) ).

  • Posts: 730

    @yojimbo2000 - yep Ghostbusters!

    All graphics done using inkpad.

    I've been toying with variations on the dispatching methods. I could increase from the 5 current ones (up/down slide, left/right slice, tap, spin and pinch) by differentiating between clockwise and anticlockwise spins and double/triple taps as well. This might go in different game modes. At the moment limiting to swipes only but ramping up the appearance rate makes for quite a challenge. The pinch and spin gestures are also not as robust as I'd like them to be, which can be frustrating if they are picked up incorrectly

  • Posts: 730

    Added a few new backgrounds. The snowflakes also respond to swipes. Will use most of them as bonus round backgrounds keeping the straightforward spooky ones as the main gameplay ones

  • Posts: 398

    Ha! Good work @West - love the Star Wars inspired one towards the end.. made me chuckle!!! :-D

  • Posts: 3

    Great backgrounds! =D>

  • Posts: 730

    Cheers! It's much smoother on the actual iPad - video capture seems to slow it down quite a bit.

  • Posts: 2,042

    @West, these backgrounds are great! Very impressive

  • Posts: 730

    @JakAttak - thanks!

  • Posts: 730

    Testing out a "bonus" ghost which needs to be "trimmed" down to reveal a bonus (in this case a heart). May set different conditions for the bonus ghost (eg time limits, limited slices to release bonus, no more than x fragments on screen at once)

  • Posts: 289

    if add a few skulls and skeletons?

  • Posts: 730

    Thanks for the suggestion but I'll concentrate on ghosts at the moment

  • Posts: 1,255

    What a lovely game.

  • Posts: 730

    Thanks @Mark

Sign In or Register to comment.