Jump to content

Minimap


havenphillip
 Share

Recommended Posts

I've been messing around with Macklebee's minimap script and I got it working but I want to find a way to get a radar sort of function to it so I can see 2d dots of where enemies are in relation to me on the minimap. I'm a noob and I get the feeling this may be out of my ability range but how can I create a 2d dot that follows a 3d character as it moves around? And also how can I have the pick show more than one enemy on the map? A "for" loop, maybe? What direction do I go from here?
 

I hid the minimap shader that goes with this script at the bottom:
 

Mini Map.lua

Link to comment
Share on other sites

  1. The first thing you need to is gather information about which enemies are near you and should be projected on the minimap.
    1. one way is to gather enemies is using https://www.leadwerks.com/learn?page=API-Reference_Object_World_ForEachEntityInAABBDo
    2. Or you can loop over all enemies in an array, and check the distance to the player. 
  2. second step is adding this information to the shader. I have never added an array of positions to a shader (I recon something like this should be possible: uniform vec2 enemyPositions[2];) but as an alternative you can add several enemy positions.

 

Link to comment
Share on other sites

Yeah ok. I guessed I would need a bounding box. I've been messing with the bounding box with little success. I found a script in the forum and was able to make a bounding box that is the size of the level and turns enemies red, but I can't from there draw any context. What would I use in the shader to represent the enemy positions? I used parts from your Project Saturn inventory item script to create a 2d box that I can size and position to the minimap, and I had some success using a camera pick but the camera pick only does one at a time and I need to do all of them within a given range. I'm thinking I can just overlay the 2d information on top of the shader rather than dealing with the shader (the shader code looks like complete nonsense to me) if only I can figure out how to get context:DrawRect to follow the bad guys around. I think I'm tracking with you. Here's my script currently. I put exclamation points near the changes I'm working on:

 

Mini Map.lua

Link to comment
Share on other sites

Try something like this.

  • You are currently using a certain area around you to show as a map. 
  • Place a sphere as a child of your player and make it invisible (or partially transparent for debugging)
  • The sphere needs to be roughly the size of the minimap you want to display. For example 4 meters wide.
  • In your minimap script, reference the sphere and perform ForEachEntityInAABBDo every second (or as often you would like to update the minimap).
  • The entities returned by the above function can now be looped over
    • Check if it is an enemy (GetKeyValue could be used)
    • Since AABB is a box and not a sphere, do a distance check to the enemy. If it is within the radius of the sphere, it should be shown on the minimap.
    • The method above is just a way to get a list of entities that are near you. The distance check is to further finetune the result. 

See if you can get this working first before worrying about drawing the positions of the enemies.

enemy.jpg.7fc5ad8bcbfcdb8428f048429346b210.jpg

  • Like 2
  • Upvote 1
Link to comment
Share on other sites

Ahh ok!  I have it working up to that point now. I didn't realize I could just pivot the whole box to the player. Enemies turn red at the distance when they enter the box, which follows the sphere, which is pivoted to the player, so it follows the player. Makes a lot of sense. Now I can also reference them with the GetKeyValue and that's something I've been stuck on for days. I'm not sure what you mean by updating the ForEachEntityInAABBDo. Do you just put it under UpdateWorld()?

The next thing is drawing the dots. I've been scratching my head for several days since once I call the loop(is that the right terminology?) for some reason I can't use anything I previously have, like anything that uses "self..." What do I need to do next to get to where I can draw the positions of the enemies? Feels trapped in that "Callback" function.

Here's where I'm at:

 

    local v = Vec3(1,0,0)
    sphere = Model:Sphere(8,self.player)
    sphere:SetScale(50,50,50)
    sphere:Hide()
    sphere:SetPosition(self.player:GetPosition())
    position = sphere:GetPosition()
    aabb = AABB(position.x+50,position.y+50,position.z+50,position.x-50,position.y-50,position.z-50)
    world:ForEachEntityInAABBDo(aabb,"Callback",v)
end

function Callback(entity,extra)
    if entity:GetKeyValue("type") == "enemy" then
        local v = Vec3(0,0,0)
        v = extra
        entity:SetColor(v.r,v.g,v.b)
    end
end

Link to comment
Share on other sites

Well done, you are making progress.

Some feedback on your code and a few suggestions:

  • Have an  array which holds enemy positions.
  • You are currently creating a new AABB from scratch, which is fine. The sphere however already has an AABB and you can simply retrieve it by using GetAABB(). Saves you from making an AABB from scratch. But that is up to you.
  • A Callback function no longer has acces to the 'self' variable. That is why you can simply provide an extra variable which either holds the script's entity.

You can try it out. When that works, we have a look at the drawing part.

**EDIT: added a simple timer in the update, so that you can control how often the enemy table is updated. 

--An array which holds the enemies. This array is empties every update before being filled again.
Script.enemyEntities = {}
Script.timer = 0 
Script.timeUntilMinimapUpdates = 0.2 

function Script:Start()
    self.miniMapSphere = Model:Sphere(8,self.player)
    self.miniMapSphere:SetScale(50,50,50)
    self.miniMapSphere:SetPosition(self.player:GetPosition(true))
    
    --Parenting the sphere to the player, means we don't have to update the position ourselves again.
    self.miniMapSphere:SetParent(self.player)
    self.miniMapSphere:Hide()
end

function Script:UpdateWorld()
    self.timer = self.timer + Time:GetSpeed()

    if self.timer > self.timeUntilMinimapUpdates then
        self.timer = 0
        self.enemyEntities = nil
        local AABB = self.miniMapSphere:GetAABB(1)

        --We pass along the entity that our current script is attached to
        world:ForEachEntityInAABBDo(AABB, "Callback", self.entity) 
    end
end

--A callback function has lost the reference to 'self', that is why pass in the 'self.entity' at ForEachEntityInAABBDo
function Callback(entity, extra)
    if entity:GetKeyValue("type") == "enemy" then
        table.insert(extra.script.enemyEntities, entity)
    end
end

 

  • Like 2
Link to comment
Share on other sites

OK. So far so good. No errors. I was trying to use GetAABB(1) on the enemies themselves. What does that "1" mean there? I had to resize the sphere for some reason but it isn't giving errors.

Here's the current state. I have a bunch of zombies(that I"m probably not supposed to have). I'm not sure that I fully understand arrays and tables yet but I plugged them in at the top. The "tables" documentation is showing something else.

Mini Map.lua

Link to comment
Share on other sites

In the mean time I would recommend reading up on lua basics or watch this tutorial seres on Leadwerks Lua basics. It teaches all the Lua you need for your scenario. Or at least watch the tutorial about tables.

The 'GetAABB' parameter is described in the documentation. You might need to switch 1 with 2.

https://www.leadwerks.com/learn?page=API-Reference_Object_Entity_GetAABB

  • Upvote 1
Link to comment
Share on other sites

Had a look at your script.

  • It looks like you want to fill in the enemy characters by hand? 
    • Script.enemyEntities ={}
      Script.enemyEntities[0] = "Prefabs/Characters/German Zombie 1.pfb" --path
      Script.enemyEntities[1] = "Prefabs/Characters/Gasmask Zombie.pfb" --path
      Script.enemyEntities[2] = "Prefabs/Characters/Helmet Zombie.pfb" --path
      Script.enemyEntities[3] = "Prefabs/Characters/Officer Zombie.pfb" --path
      Script.enemyEntities[4] = "Prefabs/Characters/Drag Zombie.pfb" --path
      Script.enemyEntities[5] = "Prefabs/Characters/German Zombie 10.pfb" --path
      Script.enemyEntities[6] = "Prefabs/Characters/Officer Zombie 9.pfb" --path
      Script.enemyEntities[7] = "Prefabs/Characters/Pilot Zombie.pfb" --path

      There is no need to do that since the callback function will fill up the table. Every seconds or so, it will refresh this table with the enemies that are in vicinity.

  • The enemies tables is part of the script and can be accesses using 'self.enemyEntities' when in the PostRender function

  • At the bottom of your post render you can start drawing a rectangle per enemy entity. Give it a shot. If you are really stuck I can help out.

	for key, value in pairs(self.enemyEntities) do
		--calculate the position to drawcolor
		context:DrawRect()
	end

 

Link to comment
Share on other sites

Ok that's what I didn't understand. The callback function is not something I'm at all familiar with I didn't know if I had to plug in a list of enemies there or somewhere else or at all. No need to do it by hand if you're telling me that's not necessary. I'm learning but total noob so a lot of the information won't get in my brain and stay there. I'm pretty much hanging on your every word here for now. I watched your vids on tables and I'll watch them again to reduce my obnoxious noob questions to a minimum. Tables, bounding boxes and raycasting definitely seem like invaluable principles to understand and would be cool to learn how to wield them.

Right now it's telling me bad argument #1 to 'pairs' (table expected, got nil) but I'll fiddle with it and see if I can get it to work.

Link to comment
Share on other sites

Don't worry, that is all part of becoming a developer. But you can't give up. Slowly you will get better and better. Ans asking questions is no problem as long as you try things out yourself as well.

As for your error: I made a mistake with cleaning the table. This line is incorrect:

self.enemyEntities = nil

After its first update the table is currently set to nil. And the loop no longer functions because the table is nil. That is also what the error is saying

So instead of setting it to nil we have to empty every enemy reference that is in there. Try something like this:

for k,v in pairs (self.enemyEntities) do
    self.enemyEntities[k] = nil
end

 

Link to comment
Share on other sites

Yeah it was telling me  "bad argument #1 to 'insert' (table expected, got nil)", but that seems to have resolved it.

I think you were right about having to change that "1" to a "2" in the line "local AABB = self.miniMapSphere:GetAABB(1)". I added entity:SetColor(1,0,0) in the callback as a check and they turn red in range with that number set to "2". With "1" I get nothing.

Still no PostRender() information showing up anywhere, though. This is what I have in the PostRender function.  It seems to me there is a disconnect between the callback and the PostRender?

    self.enemyEntities = {}

    for key, value in pairs(self.enemyEntities) do
        --calculate the position to drawcolor
        local pos = self.enemyEntities:GetPosition()
        context:SetColor(1,1,1,1)
        context:DrawRect(200+pos.x,200+pos.z,20,20,0,1)
    end

Link to comment
Share on other sites

1 hour ago, havenphillip said:

        local pos = self.enemyEntities:GetPosition()
        context:DrawRect(200+pos.x,200+pos.z,20,20,0,1)

That is not going to work since you are using the 3d position of the player and you use that as the 2d drawing coordinates. I assume you want to draw it on your minimap right? I will do my best to explain because it is a little tricky. Luckely I have access to high quality programmer art to aid in this process.

 

  1. I am going to calculate X. Try to see if you can do it for Y.
  2. Lets say the player is at x coordinate 50
  3. The sphere has a size of  60.
  4. That means the outerleft of the sphere is at 20
  5. Enemy X is at 35.
  6. Subtract enemyX - outerLeftX = 15
  7. divide 15 by the spheresize: 15/60=0.25
  8. This 0.25 is the ratio to draw with.
  9. So if your minimap is 200 pixels width, multiply it with the ratio of 0.25 and you get 40. That is the enemyDrawX coordinate inside the map.
  10. So the first argument in DrawRect: (x +  enemyDrawX, - - ,

enemy.jpg.2ff91d62bc1e18d34ae977c40a9dd498.jpg

 

 

  • Like 1
Link to comment
Share on other sites

Ha! Dang! Man, you're so far beyond me, this is embarrassing. I'm still trying to figure out why I'm not getting a rectangle anywhere on the screen at all! I'm starting to feel bad dragging you into this like I should have just gave up and left it to the veterans.

I'll give it a shot: Half of 60 is 30 so 50-30 = 20 and 50+30 = 80.

If the same law applies then the outerTopY would be 80, and the outerBottomY would be 20. EnemyX is at 35.

So 35-80=-45, then - 45/60 = -0.75.

200 * -0.75 = - 150? Or did I get the top and bottom backwards?

enemyDrawX = 40

enemyDrawY = -150

context:DrawRect(x + enemyDrawX, y + enemyDrawY, self.miniMapSize.x/25, self.miniMapSize.y/25, 0, 1)

Link to comment
Share on other sites

Ah yes! I got a rectangle on the screen when in range. For some reason when I deleted "self.enemyEntities = {}" from under PostRender() and put it under Start() all of a sudden now I get a rectangle. -150 is way off the map so clearly that's wrong. outerTopY must be 50-30=20, then?

I guess the question now is to find an equation that will relate the enemyEntities to the player

Link to comment
Share on other sites

Ah ok, I figured some of it was arbitrary. Nice, art, too, haha. As long as it does the job, right?

Here it is currently. I haven't really attempted to establish the relative coordinates, which is what I presume you are trying to explain. I'm slow at math but by all means explain it if you feel up to it.. I'm trying to think of ways to do it. One thing I was thinking was maybe making another box fron the script that is the size of the level and relating the enemies to it that way? Another thing I was trying to work out is this equation : local X2= (x + miniMapSize/2 + (enemy.x * player.z - enemy.z * player.x) /self.zoom...and do the same for Y2, but this gives me a slope, and my math sucks so I don't know how to get past that. Idk if I need some kind of quadratic equation or something. If you already have it figured out I'd be happy to learn it. Seems like I'd have to get the enemyEntity positions at some point. Right now I'm getting a rectangle on the screen for each entity in range but they all show on screen at the same spot, and they don't move yet. Changing the 3d position to 2d position is something I'm trying to get a handle on. But I"m stoked that it''s even this far!

 

 

Mini Map.lua

Link to comment
Share on other sites

Something like this?

Script.radar = 60 -- int "Radar Range"
Script.miniMapSize = Vec2(200,200) --Vec2 "Minimap Size"

 

   for k, v in pairs(self.enemyEntities) do
        --calculate the position to drawcolor
        local enemy = self.enemyEntities[k]:GetPosition()
        local pos = self.player:GetPosition(true)
        local enemyDrawX = (enemy.x - (pos.x - self.radar/2) /self.radar * self.miniMapSize.x)
        local enemyDrawY = (enemy.z - (pos.z - self.radar/2) /self.radar * self.miniMapSize.y)

        context:SetColor(1,0,0,1)
        context:DrawRect(x + enemyDrawX, y + enemyDrawY, self.miniMapSize.x/25, self.miniMapSize.y/25, 0, 1)
    end


 

Link to comment
Share on other sites

Actually, this works:

    for k, v in pairs(self.enemyEntities) do
        --calculate the position to drawcolor
        local enemy = self.enemyEntities[k]:GetPosition()
        --local pos = self.player:GetPosition(true)
        local enemyDrawX = (enemy.x - pos.x) + (self.radar / 2 / self.radar * self.miniMapSize.x)
        local enemyDrawY = (pos.z - enemy.z) + (self.radar / 2 / self.radar * self.miniMapSize.y)
            
        context:SetColor(1,0,0,1)
        context:DrawRect(x + enemyDrawX, y + enemyDrawY, self.miniMapSize.x/25, self.miniMapSize.y/25, 0, 1)
    end

 

I still need to work on trying to adjust the spacing on enemy dots to fit with the adjustable zoom so that they seem more spread out when zoom in and more clustered when zoomed out.

Here's the latest with this updated in it. It works!

Mini Map.lua

Link to comment
Share on other sites

Well done. Some feedback:

  • The calculation for the draw width is now taking place within the loop. With 10 enemies you would be doing that calculation 20 times. Instead calculate it once before the loop and store it in a variable. This makes your drawing code also easier to read. You will not notice an actual difference in performance but it is good practice to do so.
local miniMapEnemySize = Vec2(self.miniMapSize.x/25,self.miniMapSize.x/25)
  • The same thing can be done for calculating the enemy pos.
--these lines: 
(self.radar / 2 / self.radar * self.miniMapSize.y)
(self.radar / 2 / self.radar * self.miniMapSize.x)

-- Can be shortened and stored in a variable before the loop:
local radarHalf = Vec2(self.miniMapSize.x / 2, self.miniMapSize.y / 2)

--and in the loop:
local enemyDrawX = (enemy.x - pos.x) + radarHalf.x
local enemyDrawY = (enemy.y - pos.y) + radarHalf.y

 

As for your zoom in scaling. The radar variable, should adjust to the zoom level. Your radar has a start value of 60 and your zoom is 0.6. So when you in (zoomInc is 0.5), then you need to decrease the radar value. If you would zoom out, the radar value needs to increase. Note that this should also affect the minimapSphere where you perform the GetAABB() on.

 

Link to comment
Share on other sites

On 2/18/2018 at 12:21 AM, havenphillip said:

 

Like this? I think I understand. You're saying keep as much info out of the loop so it doesn't have to re-read it all everytime.

    local miniMapEnemySize = Vec2(self.miniMapSize.x/25,self.miniMapSize.y/25)
    local radarHalf = Vec2((self.radar / 2 / self.radar) * (self.miniMapSize.x), (self.radar / 2 / self.radar) * (self.miniMapSize.y))
    local dotX = (self.miniMapSize.x / self.cameraRng / self.zoom)
    local dotY = (self.miniMapSize.y / self.cameraRng / self.zoom)

    for k, v in pairs(self.enemyEntities) do
        if self.enemyEntities[k].script.enabled == true then
            if self.enemyEntities[k].script.mode ~= "dying" and self.enemyEntities[k].script.mode ~= "dead" then
                enemy = self.enemyEntities[k]:GetPosition()
                enemyDrawX = dotX * (enemy.x - pos.x) + radarHalf.x
                enemyDrawY = dotY * (pos.z - enemy.z) + radarHalf.y

                if enemyDrawX < self.miniMapSize.x and enemyDrawY < self.miniMapSize.y then
                    context:SetColor(1,0,0,1)
                    context:DrawRect(x + enemyDrawX, y + enemyDrawY, miniMapEnemySize.x, miniMapEnemySize.y, 0, 1)
                end
            end
        end
    end

The way I have it set up is almost exactly what I want. 60 is too small for the bounding sphere and the map is a little quirky. I'm trying to get the dots to disappear when they are outside of the miniMapSize.x and .y but this only works on the right border and the bottom border. Why is it not doing the same on the top and left? Need to learn Vec4?

Link to comment
Share on other sites

Note that this line 

 (self.radar / 2 / self.radar)

will always return the same value, if self.radar is above 1. For instance: (20/2) /2 = 0.5 or (80/2) / 80 = 0.5.  It is probably unlikely you will use a value smaller than 1, so I would just do self.miniMapSize.y / 2. 

 

7 hours ago, havenphillip said:

I'm trying to get the dots to disappear when they are outside of the miniMapSize.x and .y but this only works on the right border and the bottom border. Why is it not doing the same on the top and left? Need to learn Vec4?

No need for Vec4 (can be used for colors, uv coordinates etc). You are very close to achieving this btw. If you look at your if statement you can see that you only check the most outer right and outer bottom of the minimap coordinates. I believe you have the drawing coordinates of the minimap stored in x and y right? Only thing left is checking if the enemy pos lies further than those x and y coordinates. You can expand your if statement like this:

if 
	enemyDrawX >= x and
	enemyDrawX < self.miniMapSize.x and 
	enemyDrawY < self.miniMapSize.y and
	enemyDrawY >= y and
	then

I would also recommend renaming the variables 'x' and 'y' to something more clear, because there are a lot of variables in this script that do somehting with x and y. After a while you will have no idea what those x and y are referring to. Something like: Vec2 miniMapDrawPos(x, y), but that is up to you.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...