Spawn Objects Like a Commercial Game: NEVER Spawn Objects Into a Wall Ever Again (Unity Tutorial)

Spawn Objects Like a Commercial Game: NEVER Spawn Objects Into a Wall Ever Again (Unity Tutorial)

Sasquatch B Studios

1 год назад

8,530 Просмотров

Ссылки и html тэги не поддерживаются


Комментарии:

Truce Feud
Truce Feud - 07.11.2023 13:40

I have to ask. Wouldn't using an object pooler to spawn in enemies be more efficient?

Ответить
Jumpkut
Jumpkut - 22.10.2023 21:33

Great series! Have a subscribe!

Ответить
Akash
Akash - 17.08.2023 05:07

Wow! This was helpful! Thank you so much

Ответить
ME MUNDO
ME MUNDO - 24.07.2023 11:01

nice, im glad to see that my method of handling this problem was the right one, and bitwise operations were confusing only experimenting with it made me understand, just plain text hard to understand
p.s. in my case that was an airdrop, i wanted the airplane to avoid dropping it above buildings, trees and rocks, so it always land on valid terrain for players to reach it.

Ответить
Jahongir Kurbanov
Jahongir Kurbanov - 14.07.2023 20:08

Thanks for your spent precious time on helping people like me. ❤❤❤ Wish you all the best. You are the best MAN! I dont even know how to appreciate you, besides subscribing and liking. Hello from Tajikistan. Now waiting for How do a AAA companies make games.😊

Ответить
K Pickett
K Pickett - 18.06.2023 21:09

Great video!

Ответить
Martin Chyła
Martin Chyła - 18.06.2023 13:23

The stuff withh layerMask you do not understand is not really hard to realize. Its just a way of storing N boolean variables in single int variable. Its used for performance reasons, as 64bit int is a processor word size while array is more complex object that has its performance implications. Think your int not as a classic number but as an array of 64 booleans, with every bit stored into. Lets say you have 4 bit number and you have this bit flag to determine if enemy is vulnerable agains one of 4 elements: fire, air, earth and water. So instead of storing it as boolean[4], you can store it in single int and treats bits of these ints as flags. An enemy vulnerable to fire would have binary value of 0001, to air it will be 0010, earh 0100 and so on. If your enemy is vulnerable agains fire and earth, it would be like that: 0101.

But in the code (and inspectors and stuff) you are seeing this as simple int value, so enemy vuln to fire could have value of "1", enemy vuln to air - "2", enemy vuln to earth "4" and enemy vuln to water "8" (play around with some online bit to int conversions, you will quickly get how it goes). Enemy vuln to fire and earth could have a value 0101 which is 5 (1+4). If you want to add air resistance (binary 10 === 2) you could add 2 to this value, so 5+2 = 7 and 7 represents vunlerability to fire, earth and air. The thing is, doing this is not safe, because it does not check if the flag already exists on it, adding air vuln to vlaue of 7 would make int to end with a value of 9 which is resistance to fire and earth.

But we have bitwise operators that are safer to do, in these examples I assume vulnerability to earth is 2:

1) adding earth vuln to list is: vulnerabilites = vulnerabilities | 2; (binary OR)
2) removing earth vuln from list: vulnerabilites = vulnerabilities & (~2); (binary AND NOT)
3) checeking if enemy is vulnerable: if ((vulnerabilities & 2) == 2) { damage*=2; }; (binary AND + equeals)

So whats about the "<<" operator? Its binary shift left, all it does is to move bits to the left and its just easier to write this way. Lets look for above examples - you probably would not want to hardcode 2 into code, but use some enum or consts for that. So you could specify it like that:

Fire = 1, // 0001
Earth = 2, // 0010
Air = 4, // 0100
Water = 8, // 1000

But you can write air as this: (1 << 2), which means "take a value of 1 and shift all bits two places to the left). It would look like this:
1) take a value of 1: 0001
2) shift bits two places to the left: 0100

So basically, instead of manually caulculating new values 1,2,4,8,16,32,64,128,(...) you could write it like this:

Fire = 1 << 0; // 0001 << 0 = 0001
Earth = 1 << 1; // 0001 << 1 = 0010
Air = 1 << 2; // 0001 << 2 = 0100
Water = 1 << 3; // 0001 << 3 = 1000

Ответить
Darkpouetman
Darkpouetman - 15.06.2023 18:50

One way I've done it that avoids getting a random number and checking if it's correct is to get an array of all possible points in a grid, shuffle it and take the first n points, this way you don't have to keep trying in case RNGesus is angry at you.

Ответить
DomesticAbuseASMR
DomesticAbuseASMR - 10.06.2023 11:04

Playing the devil’s advocate here. Give up already. Your main game is going to fail hard. Go and do something productive instead of putting your children's’ future at stake by chasing a pipe dream. Your old job may have been soul-sucking but it paid the bills. You’re making a bad game with bad art that looks like it belongs on a free game website for kids. When you ultimately fail you’ll look back and think about how you gambled everything away for an impossible win.

Ответить
I Dont know what to watch
I Dont know what to watch - 10.06.2023 00:10

Hey do you mind sharing a way on how to implement Steam Achievements into the game? i don't see many tutorials about this out there.

Ответить
Pumpkin Dev
Pumpkin Dev - 09.06.2023 12:15

Can you do a short tutorial on Vampire Survivors like game

Ответить
Cronoo
Cronoo - 08.06.2023 22:23

Another great video.

Ответить
Alex Ávila
Alex Ávila - 08.06.2023 20:21

Thanks for the tutorial :D I loved it, keep more coming nwn

Ответить
Iiro Peltonen
Iiro Peltonen - 08.06.2023 19:39

I understand your series name and it's clickable, but I do object to Real= Commercial.

Ответить
CodeRadGames
CodeRadGames - 08.06.2023 19:29

So to help me understand bitwise operators, in this case Bitwise & operator. I always imagine as it loops through each bit and it does if statmenet on each one of them to see if it is positive or not.

For example: (5 & 3)

..8421
5 in bits is : 00000101
3 in bits is : 00000011
As you see numbers above add up to the value.

so if you do
5 & 3 you are doing basically

00000101 & 00000011

so we go through each bit from right to left and do the if statement

1 && 1 is TRUE (1)
0 && 1 is FALSE (0)
1 && 0 is FALSE (0)
rest values are 0s

00000101 & 00000011 = 00000001

So 5 & 3 is equal to 1.

Try Debug.log(5 & 3);
I would try few examples and see if you can predict value you will get. Eventually you will get hang of it.

Ответить
eyenuh300
eyenuh300 - 08.06.2023 19:00

I am not sure how well you understand binary so I am going to explain this as detailed as I can. This is going to be long.

As you are probably aware computers store everything as a binary number. So if you have an enum for collisions with values PLAYER=0, ENEMY=1, WALL=2, GROUND = 3 if you set an int to those values it will store it internally as 00, 01, 10, 11.

Now lets say the player is colliding with both the ground and an enemy at the same time? What value should we return from our collision function? 01 or 11? Maybe a list with both values? Yeah that would work but iterating over that list is costly in time, plus it also cost space to return 2 ints. We can do better.

We are allowed setup and interpret the bits anyway we want. So lets use bit values instead, remember these are binary values I am assigning to the enum, PLAYER = 0001, ENEMY = 0010, WALL = 0100, GROUND = 1000. This has the advantage where now we can return all collisions with one int. If the player is colliding with just an enemy we can return 0010, if the player collided with just the ground we return 1000. If they collided with both return 1010!

So lets say you collided with both and the function returns 1010. How do you extract that data? That is where the bitwise operator &(pronounced "and") is used. This goes bit by bit and compares the ones and zeroes. If both bits are 1, the output is set to 1 at that bit. Otherwise the output at that bit is set to 0.

So if I do something like (WALL & collisionOutput) it will do the following math
0100 //value of WALL enum
& 1010 // collisionOutput
0000 //result of & the two values

As you can see the result is 0. None of the bits of the two numbers are both one so all bits are 0. In code 0 is false when checked in an if() statement. Which is what we expect, the player did not collide with a wall.

Lets check enemies next (ENEMY & collisionOutput)
0010 //value of ENEMY enum
& 1010 // collisionOutput
0010 //result of & the two values

The result of the & is 0010. Only the second bit of the two numbers is one in both numbers. When you call an if() statement on any number other then 0 it is interpreted as true. So this will return true, your code now knows it collided with an enemy! You can do the same thing for GROUND and see that it works as well.

We are almost there, but we have a problem. Your enum isn't setup like mine where every value corresponds to a bit. Yours is just consecutive numbers 0,1,2,3. Well that is where the bit shift operator << comes in. That operator will just shift the bits of the number on the left hand side by the number on the right hand side.

So lets go back to the original values we used for the enum. If we take the value 1 and bit shift it by PLAYER, (1 << PLAYER) what do we get? Well 1 in binary is 0001 and PLAYER is 0. So we shift zero to the left, so nothing happens and the value is still 0001, which is the value of my version of the enum.

Lets try with ENEMY. So once again lets use 1 as the left hand operator (1 << ENEMY). In your version of the enum ENEMY is 1, so we shift 0001 over one place to the left. That is 0010. Which is the same value as my version of the enum! Okay lets try (1 << WALL). Your wall is 2. 0001 shifted two spaces left is 0100. Once again that is my version of the enum!

Bitwise operations are useful because you can store a lot of data into a little amount of space. Each bit represents unique data and you can send a lot of information with one int, instead of sending a bunch of ints for each piece of data.

One final thing, there is a bitwise operator | (pronounced "or"). This goes bit by bit and compares the ones and zeroes. If either number has a 1 at that bit the output is set to 1 at that bit, otherwise the output at that bit is zero.

This allows you to check multiple things in one call, making it a lot faster. So ((GROUND | WALL) & collisionOutput) will return true if you are colliding with either the ground or a wall.

Ответить
Frederic Laviale
Frederic Laviale - 08.06.2023 18:25

So what it means : (1 << collider.gameObject.layer) & _layersEnnemyConnotSpawnOn != 0 ?

LayerMask is just a combination of layers (organized as an int bitmask). There are 32 layers (0..31), one for each bit of an int (32 bits).
So if your mask is composed of layer 2 and 5 and 6 and 31, you got the mask: 10000000000000000000000000110010.

(1 << collider.gameObject.layer) build a mask containging only the gameObject layer. It take 1 and move it (left shit) to the layer position.
Ex: With layer 3 => 00000000000000000000000000000100
Ex: With layer 5 => 00000000000000000000000000010000

(1 << collider.gameObject.layer) & _layersEnnemyConnotSpawnOn, will keep only the 1 that are common to both masks.

Ex: With layer 3:
00000000000000000000000000000100
& 10000000000000000000000000110010
=> 00000000000000000000000000000000

There is no 1 in common => There is no common layer.

Ex: With layer 5:
00000000000000000000000000010000
& 10000000000000000000000000110010
=> 00000000000000000000000000010000

There is a 1 in common => There is at least one common layer.

Ответить
DrunkmanCZ
DrunkmanCZ - 08.06.2023 17:03

"How real games do it" is just such a good series!
It is really frustrating to see unusable tutorials because they are too "gamejamy"...
Thanks and keep it up!

Ответить