Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

MOBA Help
Started by rbgmob Oct 13 2018 02:17 AM

12 replies to this topic
moba minions large spawning physics help lane massive

Best Answer roaminggamer , 13 October 2018 - 09:00 AM

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/

 

 

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)



[TOPIC CONTROLS]
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

rbgmob

[GLOBAL: userInfoPane.html]
rbgmob
  • Observer

  • 5 posts
  • Corona SDK

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.



[TOPIC: post.html]
#2

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,363 posts
  • Corona SDK

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.


  • rbgmob likes this

[TOPIC: post.html]
#3

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,363 posts
  • Corona SDK

  Best Answer

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/

 

 

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)




Edited by roaminggamer, 13 October 2018 - 02:13 PM.

  • rbgmob likes this

[TOPIC: post.html]
#4

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,363 posts
  • Corona SDK

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...



[TOPIC: post.html]
#5

nick_sherman

[GLOBAL: userInfoPane.html]
nick_sherman
  • Corona Geek

  • 1,628 posts
  • Corona SDK

There's something exciting about file -> new project. I think you may addicted to it, Ed! ;)
  • Michael Flad likes this

[TOPIC: post.html]
#6

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,363 posts
  • Corona SDK

@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.



[TOPIC: post.html]
#7

XeduR @Spyric

[GLOBAL: userInfoPane.html]
XeduR @Spyric
  • Contributor

  • 361 posts
  • Corona SDK

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 :D



[TOPIC: post.html]
#8

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,363 posts
  • Corona SDK

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.


Edited by roaminggamer, 13 October 2018 - 02:12 PM.


[TOPIC: post.html]
#9

rbgmob

[GLOBAL: userInfoPane.html]
rbgmob
  • Observer

  • 5 posts
  • Corona SDK

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???



[TOPIC: post.html]
#10

rbgmob

[GLOBAL: userInfoPane.html]
rbgmob
  • Observer

  • 5 posts
  • Corona SDK

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: 

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.



[TOPIC: post.html]
#11

XeduR @Spyric

[GLOBAL: userInfoPane.html]
XeduR @Spyric
  • Contributor

  • 361 posts
  • Corona SDK

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.


  • rbgmob likes this

[TOPIC: post.html]
#12

rbgmob

[GLOBAL: userInfoPane.html]
rbgmob
  • Observer

  • 5 posts
  • Corona SDK

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 



[TOPIC: post.html]
#13

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,363 posts
  • Corona SDK

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.


Edited by roaminggamer, 14 October 2018 - 08:25 AM.



[topic_controls]
[/topic_controls]

Also tagged with one or more of these keywords: moba, minions, large, spawning, physics, help, lane, massive