Spots [Update v0.6]

Hello everyone!
I’ve created a little game I call “Spots”. It’s a game where you are a spot and you consume smaller spots to grow and avoid larger spots to survive! (Just like agar.io) I wanted to make it a little different from agar.io though, to make it feel unique. To do that i’ll be updating this game a lot. Right now there aren’t that many differences though. If you find an error, please tell me the error and what you did to cause it. If you have a suggestion, please let me know and if I like it, I will add it.

Here is the code:

--# Main

supportedOrientations(CurrentOrientation)
displayMode(FULLSCREEN)
-- Use this function to perform your initial setup
function setup()
    Menu.Begin = Button({text="Begin",colour=color(255),colourSharpness=100, size=70, pos=vec2(WIDTH/2,HEIGHT/3), callback=function()
        screen = Playing
        Menu.fadedIn = false
        Menu.alpha = 0
        tween.reset(Menu.tID)
    end})
    spots = {}
    menuImg = deadScreen()
    screen.setup()
    spotSpawningTimer = 20
    
    maximumSpots = 30
    
    fps = {}
    for k=1,10 do
        table.insert(fps,1/60)
    end
    parameter.watch(afps)
end

-- This function gets called once every frame
function draw()
    background(0)
    
    if #spots <= maximumSpots then
        spotSpawningTimer = spotSpawningTimer - 1
        if spotSpawningTimer < 0 then
            spotSpawningTimer = spotSpawningTimer + 20
            spots[#spots+1] = Spot()
        end
    end
    
    for i=#spots, 1, -1 do
        local v = spots[i]
        local remove = v:draw()
        if remove then
            table.remove(spots,i)
        end
    end
    
    screen.draw()
    
    table.remove(fps,1)
    fps[#fps+1] = DeltaTime
    local afps = 0
    for _,v in ipairs(fps) do
        afps = afps + v
    end
    afps = math.floor(10/afps)
    
    
    fill(255)
    textMode(CORNER)
    fontSize(30)
    text("FPS: "..afps,10,10)
end

function touched(t)
    screen.touched(t)
end

function deadScreen()
    local img = image(WIDTH,HEIGHT)
    setContext(img)
    img.premultiplied = false
    background(255,170)
    fill(255)
    font("Georgia-Bold")
    fontSize(100)
    text("Spots",WIDTH*0.5,HEIGHT*0.75)
    Menu.Begin:draw()
    setContext()
    return img
end

function brighten(c,a)
    return color(c.r+a,c.g+a,c.b+a,c.a)
end

function pointInRectCenter(pointX, pointY, x, y, w, h)
    return pointX >= x - w*0.5 and pointX <= x + w*0.5 and pointY >= y - h*0.5 and pointY <= y + h*0.5
end

Button = class()
function Button:init(data)
    self.callback = data.callback
    if data.pos then
        self.pos = data.pos * 1 -- * 1 to make it a new 2D vector
        elseif data.x and data.y then
        self.pos = vec2(data.x,data.y)
        else
        self.pos = vec2(0,0)
    end
    self.visible = true
    self.txt = data.text or "Button"
    self.txtC = data.textColour or color(40)
    self.size = data.size or 50
    self.font = "Futura-Medium"
    font(self.font)
    fontSize(self.size)
    self.w,self.h = textSize(self.txt)
    self.c = data.colour or color(200)
    self.cSpread = data.colourSharpness or 140
    self.mesh = mesh()
    local left = self.pos.x - self.w*0.5
    local right = self.pos.x + self.w*0.5
    local top = self.pos.y + self.h*0.5
    local bottom = self.pos.y - self.h*0.5
    self.mesh.vertices = {vec2(left,bottom),vec2(left,top),vec2(right,top)
                        ,vec2(left,bottom),vec2(right,top),vec2(right,bottom)}
    self.mesh.colors = {brighten(self.c,-self.cSpread),self.c,self.c
                        ,brighten(self.c,-self.cSpread),self.c,brighten(self.c,-self.cSpread)}
end

function Button:draw()
    if self.visible then
        self.mesh:draw()
        fill(self.txtC)
        font(self.font)
        fontSize(self.size/1.2)
        textMode(CENTER)
        textAlign(CENTER)
        text(self.txt,self.pos.x,self.pos.y)
    end
end

function Button:touched(t)
    if not self.visible then return end
    
    local pressing = pointInRectCenter(t.x,t.y,self.pos.x,self.pos.y,self.w,self.h)
    if pressing and t.state == BEGAN then
        self.mesh.colors = {self.c,brighten(self.c,-self.cSpread),brighten(self.c,-self.cSpread)
                    ,self.c,brighten(self.c,-self.cSpread),self.c}
    end
    if not pressing or t.state == ENDED then
        if pressing and t.state == ENDED then
            if self.callback then
                self.callback()
            end
        end
        self.mesh.colors = {brighten(self.c,-self.cSpread),self.c,self.c
                        ,brighten(self.c,-self.cSpread),self.c,brighten(self.c,-self.cSpread)}
    end
end

Menu = {alpha = 0}
function Menu.setup()
    Menu.mesh = mesh()
    Menu.mesh:addRect(WIDTH*0.5,HEIGHT*0.5,WIDTH,HEIGHT)
    Menu.mesh.texture = menuImg
    Menu.tID = tween(2,Menu,{alpha = 255},tween.easing.cubicOut,function() Menu.fadedIn = true end)
end

function Menu.draw()
    Menu.mesh:setColors(255,255,255,Menu.alpha)
    Menu.mesh:draw()
    if Menu.fadedIn then Menu.Begin:draw() end
    --[[
    fill(230,200,0)
    noSmooth()
    noStroke()
    rectMode(CENTER)
    rect(WIDTH/255*Menu.alpha, 100, 250, 100) ]]
end

function Menu.touched(t)
    Menu.Begin:touched(t)
end

Playing = {}
function Playing:draw()
    Player.draw()
end

function Playing.touched(t)
    Player.pos.x,Player.pos.y = Player.pos.x + (t.deltaX*1.5), Player.pos.y + (t.deltaY*1.5)
    if Player.pos.x < 0 then
        Player.pos.x = 0
        elseif Player.pos.x > WIDTH then
        Player.pos.x = WIDTH
    end
    if Player.pos.y < 0 then
        Player.pos.y = 0
        elseif Player.pos.y > HEIGHT then
        Player.pos.y = HEIGHT
    end
end

screen = Menu

Player = {diameter = 20, pos = vec2(WIDTH*0.5, HEIGHT*0.5)}
function Player.draw()
    fill(255)
    ellipse(Player.pos.x,Player.pos.y,Player.diameter)
end

Spot = class()
function Spot:init()
    self.diameter = math.random(15,250)
    local radius = self.diameter*0.5
    self.pos = vec2(0,0)
    if math.random(2) == 1 then
        if math.random(2) == 1 then
            self.pos.x = math.random(0,WIDTH)
            self.pos.y = -radius
            else
            self.pos.x = math.random(0,WIDTH)
            self.pos.y = HEIGHT+radius
        end
        else
        if math.random(2) == 1 then
            self.pos.x = -radius
            self.pos.y = math.random(0,HEIGHT)
            else
            self.pos.x = WIDTH+radius
            self.pos.y = math.random(0,HEIGHT)
        end
    end
    local speed = math.random(4,8)
    self.dir = vec2(math.random(0,WIDTH)-self.pos.x,math.random(0,HEIGHT)-self.pos.y):normalize()
    self.dir.x = self.dir.x * speed
    self.dir.y = self.dir.y * speed
    self.colour = color((255*0.2)*math.random(1,5),(255*0.2)*math.random(1,5),(255*0.2)*math.random(1,5))
end

function Spot:draw()
    local posx,posy = self.pos.x,self.pos.y
    
    pushStyle()
    
    self.pos = vec2(posx+self.dir.x,posy+self.dir.y)
    
    fill(self.colour)
    noStroke()
    ellipseMode(CENTER)
    ellipse(posx,posy,self.diameter)
    
    popStyle()
    
    local removeMe = false
    
    if screen == Playing then
        if self.pos:dist(Player.pos) <= (self.diameter+Player.diameter)*0.5 then
            if self.diameter > Player.diameter then
                Player.diameter = 20
                Player.pos = vec2(WIDTH*0.5, HEIGHT*0.5)
                screen = Menu
                screen.alpha = 0
                screen.setup()
                self.diameter = self.diameter + (Player.diameter*0.5)
                elseif self.diameter < Player.diameter then
                Player.diameter = Player.diameter + (self.diameter*0.5)
                removeMe = true
            end
        end
    end
    
    removeMe = not pointInRectCenter(posx,posy,WIDTH*0.5,HEIGHT*0.5,WIDTH+self.diameter,HEIGHT+self.diameter) or removeMe
    
    return removeMe
end

It’s great. Maybe you could make the larger fast ones a little slower.
I really feel like playing Osmos now, also a great agar.io game

Thank-you, @Kire for the feedback! When I was programming this game I was also wondering if I should make the bigger ones slower. I think I will do that for the next update. I might also make the map bigger and make the camera follow your spot, like agar.io. I know I said that I want it to be different from agar.io, but I just think the game would be better if there was more space.

Col game, but i couldn’t start for 5 seconds, there were to many circles in the middle. I reached like one minute

@Tokout Yeah, I know about that problem. I can fix that by giving the player invulnerability for a few seconds so they can get out of the way of larger spots.

New Update!:
I’m so sorry it took so long to update this game. It’s just that I was planning and later started on an FPS. But I finally got around to releasing an update!

Here is the new code!:

--# Main

supportedOrientations(CurrentOrientation)
displayMode(FULLSCREEN)
-- Use this function to perform your initial setup
function setup()
    
    -- Below are variables you can change! :D
    
    maximumSpots = 35 -- maximum spots at once
    mapW,mapH = 2000,2000 -- the size of the map
    spawnRate = 20 -- the rate at which the spots spawn
    
    -- End of variables you can change... D:
    
    Menu.Begin = Button({text="Begin",colour=color(255),colourSharpness=100, size=70, pos=vec2(0,-100), callback=function()
        screen = Playing
        Menu.fadedIn = false
        Menu.alpha = 0
        tween.reset(Menu.tID)
    end})
    Playing.border = mesh()
    do
        local b = Playing.border -- b is much faster to type than Playing.border
        
        b:addRect(-mapW*.5 - WIDTH*.25, 0, WIDTH*.5, mapH + HEIGHT) -- Left wall
        b:addRect(mapW*.5 + WIDTH*.25, 0, WIDTH*.5, mapH + HEIGHT) -- Right wall
        b:addRect(0, -mapH*.5 - HEIGHT*.25, mapW + WIDTH, HEIGHT*.5) -- Bottom wall
        b:addRect(0, mapH*.5 + HEIGHT*.25, mapW + WIDTH, HEIGHT*.5) -- Top wall
        b:setColors(255,0,0)
    end
    
    spots = {}
    menuImg = deadScreen()
    screen.setup()
    spotSpawningTimer = 0
    
    
    fps = {}
    for k=1,10 do
        table.insert(fps,1/60)
    end
end

-- This function gets called once every frame
function draw()
    background(0)
    
    if #spots < maximumSpots then
        spotSpawningTimer = spotSpawningTimer - 1
        if spotSpawningTimer < 0 then
            spotSpawningTimer = spotSpawningTimer + spawnRate
            spots[#spots+1] = Spot()
        end
    end
    
    screen.draw()
    
    table.remove(fps,1)
    fps[#fps+1] = DeltaTime
    local afps = 0
    for _,v in ipairs(fps) do
        afps = afps + v
    end
    afps = math.floor(10/afps)
    
    resetMatrix()
    fill(255)
    textMode(CORNER)
    fontSize(30)
    text("FPS: "..afps.."   Spots: "..#spots,10,10)
end

function touched(t)
    screen.touched(t)
end

function drawSpots()
    for i=#spots, 1, -1 do
        local v = spots[i]
        local remove = v:draw()
        if remove then
            table.remove(spots,i)
        end
    end
end

function deadScreen()
    local img = image(WIDTH,HEIGHT)
    setContext(img)
    img.premultiplied = false
    background(255,170)
    fill(255)
    font("Georgia-Bold")
    fontSize(100)
    text("Spots",WIDTH*0.5,HEIGHT*0.75)
    translate(WIDTH*.5,HEIGHT*.5)
    Menu.Begin:draw()
    setContext()
    return img
end

function brighten(c,a)
    return color(c.r+a,c.g+a,c.b+a,c.a)
end

function pointInRectCenter(pointX, pointY, x, y, w, h)
    return pointX >= x - w*0.5 and pointX <= x + w*0.5 and pointY >= y - h*0.5 and pointY <= y + h*0.5
end

Button = class()
function Button:init(data)
    self.callback = data.callback
    if data.pos then
        self.pos = data.pos * 1 -- * 1 to make it a new 2D vector
        elseif data.x and data.y then
        self.pos = vec2(data.x,data.y)
        else
        self.pos = vec2(0,0)
    end
    self.visible = true
    self.txt = data.text or "Button"
    self.txtC = data.textColour or color(40)
    self.size = data.size or 50
    self.font = "Futura-Medium"
    font(self.font)
    fontSize(self.size)
    self.w,self.h = textSize(self.txt)
    self.c = data.colour or color(200)
    self.cSpread = data.colourSharpness or 140
    self.mesh = mesh()
    local left = self.pos.x - self.w*0.5
    local right = self.pos.x + self.w*0.5
    local top = self.pos.y + self.h*0.5
    local bottom = self.pos.y - self.h*0.5
    self.mesh.vertices = {vec2(left,bottom),vec2(left,top),vec2(right,top)
                        ,vec2(left,bottom),vec2(right,top),vec2(right,bottom)}
    self.mesh.colors = {brighten(self.c,-self.cSpread),self.c,self.c
                        ,brighten(self.c,-self.cSpread),self.c,brighten(self.c,-self.cSpread)}
end

function Button:draw()
    if self.visible then
        self.mesh:draw()
        fill(self.txtC)
        font(self.font)
        fontSize(self.size/1.2)
        textMode(CENTER)
        textAlign(CENTER)
        text(self.txt,self.pos.x,self.pos.y)
    end
end

function Button:touched(t)
    if not self.visible then return end
    
    local pressing = pointInRectCenter(t.x,t.y,self.pos.x,self.pos.y,self.w,self.h)
    if pressing and t.state == BEGAN then
        self.mesh.colors = {self.c,brighten(self.c,-self.cSpread),brighten(self.c,-self.cSpread)
                    ,self.c,brighten(self.c,-self.cSpread),self.c}
    end
    if not pressing or t.state == ENDED then
        if pressing and t.state == ENDED then
            if self.callback then
                self.callback()
            end
        end
        self.mesh.colors = {brighten(self.c,-self.cSpread),self.c,self.c
                        ,brighten(self.c,-self.cSpread),self.c,brighten(self.c,-self.cSpread)}
    end
end

Menu = {alpha = 0}
function Menu.setup()
    Menu.mesh = mesh()
    Menu.mesh:addRect(0,0,WIDTH,HEIGHT)
    Menu.mesh.texture = menuImg
    Menu.tID = tween(1,Menu,{alpha = 255},tween.easing.cubicOut,function() Menu.fadedIn = true end)
end

function Menu.draw()
    translate(WIDTH*.5,HEIGHT*.5)
    drawSpots()
    Menu.mesh:setColors(255,255,255,Menu.alpha)
    Menu.mesh:draw()
    if Menu.fadedIn then Menu.Begin:draw() end
end

function Menu.touched(t)
    local t = {x=t.x-WIDTH*.5,y=t.y-HEIGHT*.5,state=t.state}
    Menu.Begin:touched(t)
end

Playing = {}
function Playing:draw()
    if Playing.movement then
        Player.pos.x, Player.pos.y = Player.pos.x + Playing.movement.x, Player.pos.y + Playing.movement.y
    end
    local hlfW,hlfH = mapW*.5,mapH*.5
    if Player.pos.x < -hlfW then
        Player.pos.x = -hlfW
        elseif Player.pos.x > hlfW then
        Player.pos.x = hlfW
    end
    if Player.pos.y < -hlfH then
        Player.pos.y = -hlfH
        elseif Player.pos.y > hlfH then
        Player.pos.y = hlfH
    end
    translate(-Player.pos.x,-Player.pos.y)
    translate(WIDTH*.5,HEIGHT*.5)
    Player.draw()
    drawSpots()
    Playing.border:draw()
end

function Playing.touched(t)
    if t.state == ENDED or t.state == CANCELLED then
        Playing.movement = nil
        else
        Playing.movement = vec2(t.x - WIDTH/2, t.y - HEIGHT/2):normalize()* (1/(Player.diameter*.005))*Player.speed
    end
end

screen = Menu

Player = {diameter = 20, pos = vec2(0, 0), speed = readLocalData("Player Speed",1)}
function Player.draw()
    fill(255)
    ellipse(Player.pos.x,Player.pos.y,Player.diameter)
end

Spot = class()
function Spot:init()
    self.diameter = math.random(15,250)
    local radius = self.diameter*0.5
    
    local hlfW,hlfH = mapW*.5,mapH*.5
    self.pos = vec2(0,0)
    if math.random(2) == 1 then
        if math.random(2) == 1 then
            self.pos.x = math.random(-hlfW,hlfW)
            self.pos.y = -hlfH - radius
            else
            self.pos.x = math.random(-hlfW,hlfW)
            self.pos.y = hlfH+radius
        end
        else
        if math.random(2) == 1 then
            self.pos.x = -hlfW - radius
            self.pos.y = math.random(-hlfH,hlfH)
            else
            self.pos.x = hlfW+radius
            self.pos.y = math.random(-hlfH,hlfH)
        end
    end
    
    local speed = 1/(self.diameter*.005)
    self.dir = vec2(math.random(-hlfW,hlfW)-self.pos.x,math.random(-hlfH,hlfH)-self.pos.y):normalize()
    self.dir.x = self.dir.x * speed
    self.dir.y = self.dir.y * speed
    self.colour = color((255*0.2)*math.random(1,5),(255*0.2)*math.random(1,5),(255*0.2)*math.random(1,5))
end

function Spot:draw()
    local posx,posy = self.pos.x,self.pos.y
    pushStyle()
    
    self.pos = vec2(posx+self.dir.x,posy+self.dir.y)
    
    fill(self.colour)
    noStroke()
    ellipseMode(CENTER)
    ellipse(posx,posy,self.diameter)
    
    popStyle()
    
    local removeMe = false
    
    if screen == Playing then
        if self.pos:dist(Player.pos) <= (self.diameter+Player.diameter)*0.5 then
            if self.diameter > Player.diameter then
                Player.diameter = 20
                Player.pos = vec2(0,0)
                screen = Menu
                screen.alpha = 0
                screen.setup()
                self.diameter = self.diameter + (Player.diameter*0.5)
                elseif self.diameter < Player.diameter then
                Player.diameter = Player.diameter + (self.diameter*0.5)
                removeMe = true
            end
        end
    end
    
    removeMe = not pointInRectCenter(posx,posy,0,0,mapW+self.diameter,mapH+self.diameter) or removeMe
    
    return removeMe
end

What’s new?:
-Changed the controls.
-The map is now bigger!
-The camera now follows the player (because the map cannot fit on the screen)
-Bigger blobs are slower than smaller ones.
-And a few smaller changes.

Sorry the update is kinda small. I made it within a few hours.

Enjoy! :smile: