MOBA Help

Hey Corona forums,

I am just wondering, what is the best practice for spawning a handful (maybe into the thousands) of lane minions (contemplating giving them individual physics bodies), that, let’s say, spawn on the left side of the screen, at the left base and also spawn on the right side, at the right base.

and these minions are to go down fixed points in a lane and fight each other when an enemy minion enters their aggressive circle radius.

thanks,

Jarrad T.

I wouldn’t use physics for this.

If the actors are in a known lane and that lane has a known y-position, or limited y-range, you only have to check x-positions to see if two actors are near each other (i.e. colliding).

I would move the actors with a single enterFrame listener that iterates over two tables:

  • actorsLeft - Started on left
  • actorsRight - Started on right

You can move the actors at varied rates by storing their rate as a field on the actors when you spawn the actor, then use the algorithm in the enterFrame listener to calculate how far the actor has moved since the last frame (as a factor of delta time).

PS - Fun question.  Thanks for asking.

This is how I would prototype such a game:

https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2018/10/laneFighter.zip

( Be warned this prototype is a bit advanced in some places and uses SSK because I’m way too lazy to do longhand Corona.  That said, you should still be able to read the code and understand essentially what I’ve done. )

SSK ==> https://roaminggamer.github.io/RGDocs/pages/SSK2/

https://www.youtube.com/watch?v=sLY3O0DogWs&feature=youtu.be

io.output():setvbuf("no") display.setStatusBar(display.HiddenStatusBar) -- ===================================================== require "ssk2.loadSSK" \_G.ssk.init( { measure = false } ) ssk.meters.create\_fps(true) ssk.meters.create\_mem(true) --ssk.misc.enableScreenshotHelper("s") -- ===================================================== -- ===================================================== local mAbs = math.abs local mRand = math.random local getTimer = system.getTimer local actorSize = 40 local fightDist = 32 local lanePoints = {} local leftGuys = {} local rightGuys = {} local leftScore = 0 local rightScore = 0 local lanes = 4 local laneWidth = actorSize \* 2 local speedUp = 1 -- for debugging and speeding up whole 'game' to test faster local fightTime = 500/speedUp local spawnPeriod = 1000/speedUp -- ===================================================== -- HUDS to count left and right guys how are currently alive -- ===================================================== local layers = ssk.display.quickLayers( nil, "background", "content", "foreground" ) local back = ssk.display.newImageRect( layers.background, centerX, centerY, "protoBackX.png", { w = 720, h = 1386, rotation = 90 } ) local leftGuysHUD = ssk.display.newRect( layers.foreground, left+150, bottom-50, { w = 280, h = 80, fill = \_DARKERGREY\_ } ) leftGuysHUD.countLabel = display.newText( layers.foreground, 0, leftGuysHUD.x - 280/4, leftGuysHUD.y, nil, 20 ) leftGuysHUD.scoreLabel = display.newText( layers.foreground, 0, leftGuysHUD.x + 280/4, leftGuysHUD.y, nil, 20 ) leftGuysHUD.scoreLabel:setFillColor(1,1,0,0.5) local rightGuysHUD = ssk.display.newRect( layers.foreground, right-150, bottom-50, { w = 280, h = 80, fill = \_DARKERGREY\_ } ) rightGuysHUD.countLabel = display.newText( layers.foreground, 0, rightGuysHUD.x - 280/4, rightGuysHUD.y, nil, 20 ) rightGuysHUD.scoreLabel = display.newText( layers.foreground, 0, rightGuysHUD.x + 280/4, rightGuysHUD.y, nil, 20 ) rightGuysHUD.scoreLabel:setFillColor(0,1,0,0.5) -- ===================================================== -- Draw the lanes -- ===================================================== local curY = centerY - (lanes/2 ) \* laneWidth local tmp = display.newLine( layers.background, left, curY, right, curY ) tmp:setStrokeColor(1,0,0) tmp.strokeWispawnDTh = 3 for i = 1, lanes do curY = curY + laneWidth tmp = display.newLine( layers.background, left, curY, right, curY ) tmp:setStrokeColor(unpack(\_ORANGE\_)) tmp.strokeWispawnDTh = 2 end tmp.strokeWispawnDTh = 3 tmp:setStrokeColor(1,0,0) -- ===================================================== -- Draw boxes to mark the starting positions of lanes -- ===================================================== local curY = centerY - (lanes/2 ) \* laneWidth + laneWidth/2 for i = 1, lanes do lanePoints[#lanePoints+1] = { ssk.display.newRect( layers.background, left + laneWidth/2, curY, { size = laneWidth/2, fill = \_T\_, stroke = \_Y\_, strokeWispawnDTh = 2 } ), ssk.display.newRect( layers.background, right - laneWidth/2, curY, { size = laneWidth/2, fill = \_T\_, stroke = \_G\_, strokeWispawnDTh = 2 } ) } curY = curY + laneWidth end -- ===================================================== -- Left/Right Guy spawner -- ===================================================== local function spawnGuy( side ) side = side or 1 -- 1, 2 == left, right -- Select random lane to start in local lane = mRand( 1, lanes ) -- Determine x,y starting position local x = lanePoints[lane][side].x local y = lanePoints[lane][side].y -- Make an actor display object local actor = ssk.display.newImageRect( layers.content, x, y, (side == 1 ) and "leftGuy.png" or "rightGuy.png", { size = actorSize } ) -- Give actor some attributes actor.speed = mRand( speedUp \* 50, speedUp \* 75 ) actor.side = side actor.fighting = false actor.lane = lane -- Store reference to actor indexed by its display object handle if( side == 1 ) then leftGuys[actor] = actor else rightGuys[actor] = actor end return actors end -- ===================================================== -- Enter Frame Listener To Do All Work -- ===================================================== local lastSpawnTime = getTimer() - spawnPeriod local lastMoveTime = getTimer() local function enterFrame() local curT = getTimer() -- Time to spawn new guys? local spawnDT = curT - lastSpawnTime if( spawnDT \>= spawnPeriod ) then lastSpawnTime = curT spawnGuy(1) spawnGuy(2) end -- Update counts of guys on their HUDs leftGuysHUD.countLabel.text = table.count( leftGuys ) rightGuysHUD.countLabel.text = table.count( rightGuys ) -- Move Everyone that isn't fighting -- Also check to see if they reached their goal and if so, -- remove the player local moveDT = curT - lastMoveTime lastMoveTime = curT local dx for \_,guy in pairs( leftGuys ) do if( not guy.fighting ) then dx = guy.speed \* moveDT/1000 guy.x = guy.x + dx -- if( guy.x \> right ) then leftGuys[guy] = nil display.remove( guy ) rightScore = rightScore + 1 rightGuysHUD.scoreLabel.text = rightScore end end end for \_,guy in pairs( rightGuys ) do if( not guy.fighting ) then dx = -guy.speed \* moveDT/1000 guy.x = guy.x + dx -- if( guy.x \< left ) then rightGuys[guy] = nil display.remove( guy ) leftScore = leftScore + 1 leftGuysHUD.scoreLabel.text = leftScore end end end -- Check to see if anyone is near enought to fight local adx for \_, leftGuy in pairs( leftGuys ) do if( not leftGuy.fighting ) then for \_, rightGuy in pairs( rightGuys ) do if( not rightGuy.fighting ) then if( ( leftGuy.lane == rightGuy.lane ) and mAbs( leftGuy.x - rightGuy.x ) \<= fightDist ) then leftGuy.fighting = true rightGuy.fighting = true leftGuy:setFillColor(1,0,0) rightGuy:setFillColor(1,0,0) -- -- Wait fightTime and randomly choose one guy to win fight -- timer.performWithDelay( fightTime, function() if(mRand(1,2) == 2 ) then leftGuys[leftGuy] = nil display.remove(leftGuy) rightGuy.fighting = false rightGuy:setFillColor(1,1,1) else rightGuys[rightGuy] = nil display.remove(rightGuy) leftGuy.fighting = false leftGuy:setFillColor(1,1,1) end end ) end end end end end end Runtime:addEventListener("enterFrame", enterFrame)

Be warned.  There may be a scoring bug in the prototype!  Scores should be awarded when guys go off the edge of the screen.  I’m not seeing the right side guys get scores properly though…

There’s something exciting about file -> new project. I think you may addicted to it, Ed! :wink:

@nick_sherman,

Yeah.  I’m overdoing it again lately with the ‘ask Ed’ responses.  While that only took about an hour to make, I probably should have limited my time.

I do get a kick out of solving problems though and that particular mechanic was one on my list of: try it out some time’ mechanics. 

I think there is room for a game that combines Plants VS Zombie mechanics and ‘something else’.  The lane fighter concept (not sure why op called this MOBA in title) is part of that.

Another great prototype, Ed!

Just out of curiosity, was this what you were looking for, rbgmob? I mean, it is what you sort of described, but when I first read moba, I personally thought about games like Heroes of the Storm, League of Legends or Dota 2 :smiley:

Xedur@Spyric - I was saying that the OP used the term MOBA in the title. 

First, I always suggest one NOT use a acronym w/o spelling it once.  

Second, to me MOBA means “Multiplayer Online Battle Arena”.  

So, I was confused by the question and the mechanic it asked about, since it didn’t (in my evaluation) seem related to the title.  Still I tried to answer it as best I could. 

Note: I often suggest that when one is asking about a mechanic that one should include a youtube video link to a game that uses that mechanic and then give us the time.

Often times, folks will use words to describe a mechanic that may be wrong or misunderstood. 

Whereas, if you tell me, “Watch this video and see time 0:23.  That is what I want to do.” I should be able to grok your intent.

wowza… honestly, I wasn’t expecting a full blown prototype straight away… I was thinking there was going to be more of a discussion at hand, to figure out what exactly I was after… but this looks like, with a bit of tweaking… what I need to do the job, so thanking you roaminggamer for your insight!

funnily enough, what you have posted is somewhat similar to concepts I have already divulged. I suppose as a side question… what exactly is SSK, and how is that coming into play here???

just to add on, this is more to the point what I’m trying to accomplish, as heading roaminggamer’s advice with time stamping a video.

so, if you go to this video, it’s a mod that was made for starcraft, called nexus war, which is effectively what I want to try and replicate.

here’s the link: https://www.youtube.com/watch?v=bUxpO7D8IaU

and you’re looking at 16:33 in the video, as you can see there is ground troops, air troops, stealth troops, I want to be able to encompass all these things on a smaller scale I am going to presume.

thanks again.

Ed’s prototype gets you going pretty well with this.

If you are thinking of creating a Nexus Wars like “2D clone” using Corona, then you’ll have to come up with how to implement those various mechanics. Using physics, as already mentioned, is probably not a good idea as it would turn quite cumbersome very fast.

There are a lot of optimising that you could do. Using that Starcraft 2 style, you could give each unit a circular “aggro field”. If an enemy enters within a certain distance to them, then the unit begins to attack that enemy until they are destroyed. You could use the Pythagoras theorem to calculate that distance. Then include those special if-clauses for how that unit will attack. For instance, if a flying unit enters a melee unit’s aggro field then nothing happens as melee units cant attack air units, but if any non-stealthed unit enters a ranged unit’s aggro field, then they stop moving and start attacking until that enemy is destroyed. During this phase, the unit wouldn’t have to perform any aggro field calculations as it would be focused on attacking. Once the enemy dies, then resume the movement and tracking, etc.

Now, some further optimising tricks could be that you track where the furthest units for both players are. If you account for their attack ranges, then you know for certain that no units behind them can actually reach the enemy and therefore you don’t need to track their aggro fields. This would mean that even if you have hundreds or thousands of units on the map, you’d only perform those aggro field calculations for the units at the front lines.

Everything ultimately depends on your plans, i.e. what you want and how you decide to implement everything.

thanks for the reply Xedur, in actual fact, your last paragraph there on calculating aggro check on the furtherest unit makes an abundance of sense… that could potentially free up a whole bunch of memory issues that I am currently facing as I believe the problems I am currently facing is I am checking every single units aggro circle, at all times causing for huge memory issues.

so thanking you once again Xedur, that might just do the trick. really appreciate it :D 

re: SSK

SSK (https://roaminggamer.github.io/RGDocs/pages/SSK2/) is my own free library of solutions for common dev problems.

It has been around for about 6 years here (and comes from my original SSK library for Torque).

It is not perfect, but it is pretty solid.  Peruse the docs to see some of what it offers.

I never code w/o it.

re: The Video

You’re going to learn how to do basic vector math to accomplish that kind of behavior.  Aiming, firing, facing, …

You’re also going to have to implement some kind of path finding code (try jumper) to allow units to walk around complex area, objects, and avoid each other.

You’ll need to learn how to code up camera code and understand how to drag the world or select a position from a mini-map and have that choose the camera focus/center.

There is a bunch of interface work there too.

Then of course, you’ll need to learn how to give metrics to units and probably some basic state-machines to govern their behavior.

There is, much, much, …, more, but that was at first glance.

I wouldn’t use physics for this.

If the actors are in a known lane and that lane has a known y-position, or limited y-range, you only have to check x-positions to see if two actors are near each other (i.e. colliding).

I would move the actors with a single enterFrame listener that iterates over two tables:

  • actorsLeft - Started on left
  • actorsRight - Started on right

You can move the actors at varied rates by storing their rate as a field on the actors when you spawn the actor, then use the algorithm in the enterFrame listener to calculate how far the actor has moved since the last frame (as a factor of delta time).

PS - Fun question.  Thanks for asking.

This is how I would prototype such a game:

https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2018/10/laneFighter.zip

( Be warned this prototype is a bit advanced in some places and uses SSK because I’m way too lazy to do longhand Corona.  That said, you should still be able to read the code and understand essentially what I’ve done. )

SSK ==> https://roaminggamer.github.io/RGDocs/pages/SSK2/

https://www.youtube.com/watch?v=sLY3O0DogWs&feature=youtu.be

io.output():setvbuf("no") display.setStatusBar(display.HiddenStatusBar) -- ===================================================== require "ssk2.loadSSK" \_G.ssk.init( { measure = false } ) ssk.meters.create\_fps(true) ssk.meters.create\_mem(true) --ssk.misc.enableScreenshotHelper("s") -- ===================================================== -- ===================================================== local mAbs = math.abs local mRand = math.random local getTimer = system.getTimer local actorSize = 40 local fightDist = 32 local lanePoints = {} local leftGuys = {} local rightGuys = {} local leftScore = 0 local rightScore = 0 local lanes = 4 local laneWidth = actorSize \* 2 local speedUp = 1 -- for debugging and speeding up whole 'game' to test faster local fightTime = 500/speedUp local spawnPeriod = 1000/speedUp -- ===================================================== -- HUDS to count left and right guys how are currently alive -- ===================================================== local layers = ssk.display.quickLayers( nil, "background", "content", "foreground" ) local back = ssk.display.newImageRect( layers.background, centerX, centerY, "protoBackX.png", { w = 720, h = 1386, rotation = 90 } ) local leftGuysHUD = ssk.display.newRect( layers.foreground, left+150, bottom-50, { w = 280, h = 80, fill = \_DARKERGREY\_ } ) leftGuysHUD.countLabel = display.newText( layers.foreground, 0, leftGuysHUD.x - 280/4, leftGuysHUD.y, nil, 20 ) leftGuysHUD.scoreLabel = display.newText( layers.foreground, 0, leftGuysHUD.x + 280/4, leftGuysHUD.y, nil, 20 ) leftGuysHUD.scoreLabel:setFillColor(1,1,0,0.5) local rightGuysHUD = ssk.display.newRect( layers.foreground, right-150, bottom-50, { w = 280, h = 80, fill = \_DARKERGREY\_ } ) rightGuysHUD.countLabel = display.newText( layers.foreground, 0, rightGuysHUD.x - 280/4, rightGuysHUD.y, nil, 20 ) rightGuysHUD.scoreLabel = display.newText( layers.foreground, 0, rightGuysHUD.x + 280/4, rightGuysHUD.y, nil, 20 ) rightGuysHUD.scoreLabel:setFillColor(0,1,0,0.5) -- ===================================================== -- Draw the lanes -- ===================================================== local curY = centerY - (lanes/2 ) \* laneWidth local tmp = display.newLine( layers.background, left, curY, right, curY ) tmp:setStrokeColor(1,0,0) tmp.strokeWispawnDTh = 3 for i = 1, lanes do curY = curY + laneWidth tmp = display.newLine( layers.background, left, curY, right, curY ) tmp:setStrokeColor(unpack(\_ORANGE\_)) tmp.strokeWispawnDTh = 2 end tmp.strokeWispawnDTh = 3 tmp:setStrokeColor(1,0,0) -- ===================================================== -- Draw boxes to mark the starting positions of lanes -- ===================================================== local curY = centerY - (lanes/2 ) \* laneWidth + laneWidth/2 for i = 1, lanes do lanePoints[#lanePoints+1] = { ssk.display.newRect( layers.background, left + laneWidth/2, curY, { size = laneWidth/2, fill = \_T\_, stroke = \_Y\_, strokeWispawnDTh = 2 } ), ssk.display.newRect( layers.background, right - laneWidth/2, curY, { size = laneWidth/2, fill = \_T\_, stroke = \_G\_, strokeWispawnDTh = 2 } ) } curY = curY + laneWidth end -- ===================================================== -- Left/Right Guy spawner -- ===================================================== local function spawnGuy( side ) side = side or 1 -- 1, 2 == left, right -- Select random lane to start in local lane = mRand( 1, lanes ) -- Determine x,y starting position local x = lanePoints[lane][side].x local y = lanePoints[lane][side].y -- Make an actor display object local actor = ssk.display.newImageRect( layers.content, x, y, (side == 1 ) and "leftGuy.png" or "rightGuy.png", { size = actorSize } ) -- Give actor some attributes actor.speed = mRand( speedUp \* 50, speedUp \* 75 ) actor.side = side actor.fighting = false actor.lane = lane -- Store reference to actor indexed by its display object handle if( side == 1 ) then leftGuys[actor] = actor else rightGuys[actor] = actor end return actors end -- ===================================================== -- Enter Frame Listener To Do All Work -- ===================================================== local lastSpawnTime = getTimer() - spawnPeriod local lastMoveTime = getTimer() local function enterFrame() local curT = getTimer() -- Time to spawn new guys? local spawnDT = curT - lastSpawnTime if( spawnDT \>= spawnPeriod ) then lastSpawnTime = curT spawnGuy(1) spawnGuy(2) end -- Update counts of guys on their HUDs leftGuysHUD.countLabel.text = table.count( leftGuys ) rightGuysHUD.countLabel.text = table.count( rightGuys ) -- Move Everyone that isn't fighting -- Also check to see if they reached their goal and if so, -- remove the player local moveDT = curT - lastMoveTime lastMoveTime = curT local dx for \_,guy in pairs( leftGuys ) do if( not guy.fighting ) then dx = guy.speed \* moveDT/1000 guy.x = guy.x + dx -- if( guy.x \> right ) then leftGuys[guy] = nil display.remove( guy ) rightScore = rightScore + 1 rightGuysHUD.scoreLabel.text = rightScore end end end for \_,guy in pairs( rightGuys ) do if( not guy.fighting ) then dx = -guy.speed \* moveDT/1000 guy.x = guy.x + dx -- if( guy.x \< left ) then rightGuys[guy] = nil display.remove( guy ) leftScore = leftScore + 1 leftGuysHUD.scoreLabel.text = leftScore end end end -- Check to see if anyone is near enought to fight local adx for \_, leftGuy in pairs( leftGuys ) do if( not leftGuy.fighting ) then for \_, rightGuy in pairs( rightGuys ) do if( not rightGuy.fighting ) then if( ( leftGuy.lane == rightGuy.lane ) and mAbs( leftGuy.x - rightGuy.x ) \<= fightDist ) then leftGuy.fighting = true rightGuy.fighting = true leftGuy:setFillColor(1,0,0) rightGuy:setFillColor(1,0,0) -- -- Wait fightTime and randomly choose one guy to win fight -- timer.performWithDelay( fightTime, function() if(mRand(1,2) == 2 ) then leftGuys[leftGuy] = nil display.remove(leftGuy) rightGuy.fighting = false rightGuy:setFillColor(1,1,1) else rightGuys[rightGuy] = nil display.remove(rightGuy) leftGuy.fighting = false leftGuy:setFillColor(1,1,1) end end ) end end end end end end Runtime:addEventListener("enterFrame", enterFrame)

Be warned.  There may be a scoring bug in the prototype!  Scores should be awarded when guys go off the edge of the screen.  I’m not seeing the right side guys get scores properly though…

There’s something exciting about file -> new project. I think you may addicted to it, Ed! :wink:

@nick_sherman,

Yeah.  I’m overdoing it again lately with the ‘ask Ed’ responses.  While that only took about an hour to make, I probably should have limited my time.

I do get a kick out of solving problems though and that particular mechanic was one on my list of: try it out some time’ mechanics. 

I think there is room for a game that combines Plants VS Zombie mechanics and ‘something else’.  The lane fighter concept (not sure why op called this MOBA in title) is part of that.

Another great prototype, Ed!

Just out of curiosity, was this what you were looking for, rbgmob? I mean, it is what you sort of described, but when I first read moba, I personally thought about games like Heroes of the Storm, League of Legends or Dota 2 :smiley:

Xedur@Spyric - I was saying that the OP used the term MOBA in the title. 

First, I always suggest one NOT use a acronym w/o spelling it once.  

Second, to me MOBA means “Multiplayer Online Battle Arena”.  

So, I was confused by the question and the mechanic it asked about, since it didn’t (in my evaluation) seem related to the title.  Still I tried to answer it as best I could. 

Note: I often suggest that when one is asking about a mechanic that one should include a youtube video link to a game that uses that mechanic and then give us the time.

Often times, folks will use words to describe a mechanic that may be wrong or misunderstood. 

Whereas, if you tell me, “Watch this video and see time 0:23.  That is what I want to do.” I should be able to grok your intent.