I am trying to achieve a very basic 2D game right now where an enemy is on the screen and it bounces back and forth between set points (50 and 500) kind of like a space invaders sort of thing. My issue is I can only get it to go right, but then not come back towards the left and repeat.
I was messing around with coding it myself before bothering to look into it and actually figure it out but I thought I had something that would work, but well it doesn't, my issue is I don' get why.
My code is supposed to work like a switch, two if statements within the Update loop, one comes on the other goes off, one moves it right the other moves it left, I thought that was fine but it doesn't do that. It moves it right just fine but then the left part just doesn't work.
So, why does the following code not work?
namespace _2D_game_num1
{
class Enemy
{
int health;
Vector2 enemy_location = new Vector2(50, 50);
Vector2 enemy_speed = new Vector2(1, 1);
Player player = new Player("dummy");
public Enemy()
{
health = 100;
}
public void UpdateLocation()
{
//Vector2 player_pos = player.GetLocation();
//if (player_pos.X < 200)
// Using the players location to figure out where the enemy should move
bool right = true;
bool left = false;
if (right)
{
enemy_location.X += enemy_speed.X;
if (enemy_location.X == 500)
{
right = false;
left = true;
}
}
if (left)
{
enemy_location.X -= enemy_speed.X;
if (enemy_location.X == 50)
{
right = true;
left = false;
}
}
}
public Vector2 GetLocation()
{
return enemy_location;
}
}
}
And then in the main Game class I have it so enemy1.UpdateLocation(); is within the Update section correctly (along with my players movement which works fine).
Try this:
public void UpdateLocation()
{
enemy_location.X += enemy_speed.X;
if (enemy_location.X <= 50 || enemy_location.X >= 500)
{
enemy_speed.X = new Vector2(-enemy_speed.X, enemy_speed.Y);
}
}
What we are doing here is moving the enemy based on it's current speed. Then if the location is on the left or right of the screen, change direction.
Sometimes it pays to keep things simple. Get rid of the left and right flags, as they are just confusing things. When you're dealing with Update methods you're typically changing the state of something. The way you had it before, the left and right state was getting reset every time UpdateLocation is called.
Btw, you might want to consider passing in GameTime to your Update methods. Typically, when you're moving things around in real-time you'll want to multiply movement by some kind of deltaTime to keep things smooth on all devices. You're probably getting away with it because by default it'll be a fixed frame rate, but that may not always be the case, so it's a good habit to get into.
Related
So, firstly, my scene is made out of 9 empty objects each one having spikes that have animation to pop out of the floor. I am making a game where you should avoid spikes and other projectiles. I tested this first with sprite renderer and changing colors, it worked perfectly. But when I want to activate animations using trigger (animations start from empty game state and go to their animations, then after it finishes they return to normal). I've looked trough every thing I could think of and I could not solve this. It always starts animations for 4-6 different animations and start them at the same time, but for some reason: AFTER FIRST SHOWING, every single time after first time spikes appear, 2/3 out of 4/6 always show late but start at same time. Weirdly first time it always works greatly. Any thoughts on what may cause this?
void Update()
{
if (spikeTimer > 0)
spikeTimer -= Time.deltaTime;
if (spikeTimer<0)
{
Function();
spikeTimer = spikeCooldown;
}
}
public void Function()
{
int x = Random.Range(4, 6);
List<int> included = new List<int>();
while (included.Count < x)
{
int y = Random.Range(1, 10);
if (!included.Contains(y))
{
included.Add(y);
}
}
foreach (int i in included)
{
print("aktiviran je broj" + i);
Spikes[i - 1].GetComponent<Spike>().ActivateSpike();
}
}
Now this is the ActivateSpike method that all Spikes in the scene contain
public void ActivateSpike()
{
SpriteRenderer sr = gameObject.GetComponent<SpriteRenderer>();
if (sr.color != Color.black)
{
sr.color = Color.black;
}
else
sr.color = Color.red;
/* Animator animator = gameObject.GetComponent<Animator>();
animator.SetTrigger("Activate");*/
}
You can see that I tried changing the sprite to square and changing its color, and it works perfectly...
I have a condition that makes a check. Like this:
if (moveDown)
{
Debug.Log("hit1");
if (RightTowers[rightIndex].transform.GetChild(2).transform.eulerAngles.z <= -40f)
{
moveDown = false;
moveUp = true;
}
else
{
RightTowers[rightIndex].transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite = Mirrors[mirrorIndex++];
rotateAngle = Quaternion.Euler(0f, 0f, RightTowers[rightIndex].transform.GetChild(2).eulerAngles.z - angle);
RightTowers[rightIndex].transform.GetChild(2).transform.rotation = rotateAngle;
}
}
if (moveUp)
{
Debug.Log("hit2");
if (RightTowers[rightIndex].transform.GetChild(2).transform.eulerAngles.z >= 40f)
{
moveDown = true;
moveUp = false;
}
else
{
RightTowers[rightIndex].transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite = Mirrors[mirrorIndex--];
rotateAngle = Quaternion.Euler(0f, 0f, RightTowers[rightIndex].transform.GetChild(2).eulerAngles.z + angle);
RightTowers[rightIndex].transform.GetChild(2).transform.rotation = rotateAngle;
}
}
The problem is that when the object is rotated to -40 degrees, and the condition is checked it does not deactivate moveDown and activate moveUp. He further rotates the object with angle index.
I do this rotation when I click on a button. He must, when he reaches -40 degrees, deactivate me moveDown. Why not disable it?
First things first, create some intermediate fields, your code is really heavy, and you're actually accessing the data each time because of all the nested calls you are making, and it's really making it unclear, especially for anyone outside of your project.
Secondly, I don't see the point of having 2 booleans which are the exact opposite value of each other.. you'd get the same result having just one since they are inversed.
That being said, I believe (from what I got from your snippet) that you're probably not in the degree range you might think ? I don't know if you have verified that but, your character could look totally normal, exactly like you have place him originally, but he might have a 360, 720 etc... degree rotation applied(can be negative too), so if you are not looking at the rotation between initial position and desired, you might not get the right values, maybe do a RotationDifference() func, or if (rotation == 360 or -360) rotation = 0 I'm just throwing out ideas, you really have many ways of doing it.
Lastly, your code seems quite heavy for a seemingly mundane task, you should look into that. Try to abstract your tasks, you are trying to do everything at once, instead of having sturdy systems taking care of it.
(new answer after comments)
The logic you want is not appearing "clearly" while reading the code, this makes difficult to debug and understand what is wrong !
As per your comment, I understand that you need move back and forth from -40 to 40.
First, we need to clearly separate the tasks
The two separate tasks are :
Decide in which direction tower is moving next step.
Move the tower up or down. This is basically the two 3-lines blocks of code. Let's call these blocks "MoveUp()" and "MoveDown()"
Now, write some pseudo code for it
// pseudo code
// First, decide in which direction to go
if current angle is 40° (or more),
set direction to down for next move.
else if current angle is -40 (or less),
set direction to up for next move.
else
just continue in the current direction
// Second, move the tower
if direction is down, move down
else move up.
Let's translate this to real C# code
// suppose you have a private bool variable in your class
// called "isMovingDown"
private bool isMovingDown = false; // inital value, could be true if you want
// call this in your Update or FixedUpdate logic
private void MoveYourTowerLogic() {
// First : decide in which direction to go :
var currentAngle = RightTowers[rightIndex].transform.GetChild(2).transform.eulerAngles.z;
if (currentAngle >= 40f)
{
isMovingDown = false;
}
else if (currentAngle <= -40f)
{
isMovingDown = true;
}
// else current angle is between 40 and -40
// we simply need to o nothing, because we want to keep the previous direction set.
// remember, the variable movingDown is declared at the class level,
// so it 'remembers' the last value set from last call
// Second : Move the tower
if (isMovingDown)
{
MoveDown();
}
else // tower is moving up
{
MoveUp();
}
// NOTE : I would simply create one function Move(int angle)
// to simplify this much further and avoid to write all this mostly useless code before
// but I want to keep this simple to understand for now
}
private void MoveDown()
{
// logic to move tower down
}
private void MoveUp()
{
// logic to move tower up
}
Notice how reading this is much more "human-friendly" now, you can now, I hope, read this more easily, and that will help you modify it if you want to do more complex things later.
I strongly suggest you to avoid writing the same code several time. You can notice how I simplified and wrote only once the logic to get the angle into a variable named exactly like what I was thinking in my pseudo code.
Also, to write the methods "MoveUp" and "MoveDown", you can simply copy paste your actual code into them. But then after, you could even refactor them to avoid code repetition.
Like this :
(Edit)
Let's refactor this further
Basically, the moving logic is a completely separate task, and the only difference between moving up and down is the + or - angle.
Also, maybe you want to move your towers with a different angle later. It make sense to say that the rotation of tower depends only on the angle.
So let's create a method for that has only the angle as parameter.
With something like, instead of MoveUp() and MoveDown() :
private void MoveTower(int angle)
{
RightTowers[rightIndex].transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite = Mirrors[mirrorIndex++];
rotateAngle = Quaternion.Euler(0f, 0f, RightTowers[rightIndex].transform.GetChild(2).eulerAngles.z + angle);
RightTowers[rightIndex].transform.GetChild(2).transform.rotation = rotateAngle;
}
The second part will be greatly simpler :
// Second, move the tower
MoveTower(isMovingDown ? angle : -angle);
I have run into a slight issue with the collision resolution in my game engine. If two objects collide and that collision causes the velocity to go to zero, the edges of the objects will overlap each other and they'll be stuck.
Is there a way to implement a catchall for this kind of a situation? i.e. move the objects just enough in the right direction so they are not stuck.
Here is how I am checking collisions and moving objects. When update is called on an entity, it moves the (x,y).
public static void Update()
{
for (var iterator = 0; iterator < PhysicsEntities.Count; iterator++)
{
for (var index = iterator + 1; index < PhysicsEntities.Count; index++)
{
if (!Collision.ResolveCollision(PhysicsEntities[iterator],
PhysicsEntities[index], Detection)) continue;
PhysicsEntities[iterator].Update();
PhysicsEntities[iterator].Collided = true;
PhysicsEntities[index].Update();
PhysicsEntities[index].Collided = true;
}
}
foreach (var entity in PhysicsEntities)
{
entity.Update(velocity: true);
entity.Collided = false;
}
}
}
Here is the update function for the entities:
public void Update(bool velocity = false)
{
if(!Movable) return;
if (!Collided)
{
var moveX = Velocity.X / Universe.UpdateInterval;
var moveY = Velocity.Y / Universe.UpdateInterval;
Position.Move(moveX, moveY);
BoundingBox.Move(moveX, moveY);
}
if(velocity) UniversalForces();
}
private void UniversalForces()
{
Velocity.Scale(1 - Universe.Friction);
Velocity.Add(Universe.GravityMag, Universe.GravityDir);
}
Finally, here is a image of one simulation where the objects get stuck. As you can see, it is just the edges that are getting stuck:
The quick solution is to move both objects back to the previous tic's position and any other object that causes a collision with to move back as well. It works, but it looks messy and causes some behavior that looks really bad - things like pushing directly at a wall leaves a gap, but angling towards a wall leaves a smaller gap. Very messy.
The better solution is to move both objects back just far enough along their negative velocity vector so that they are no longer touching. Usually some dot product math can give you what you need for this, though iterating backwards can work (slow).
Long story short, don't ever allow objects to overlap. Take care of it before it happens and you avoid stuck jitters, can't move stuff, etc.
When one object collides with another, get it to backtrack up its own movement vector so that the distance between the centroids of both objects is equal to the two radius. This works if its a circle - if its a complex polygon you really need to do edge collision detection instead of bounding sphere detection. If the bounding spheres collide, then you move to complex edge detection. In the end the trick is the same; check for collision then back up the movement vector until you find the exact (or nearly exact) point of collision.
I was able to figure it out with the suggestions people made. Some of the changes I made include having each object collide with any other object only once per update, and moving the object after the collision until it is no longer colliding. Here is the code that I used to do that, feel free to use this on any project and let me know if you have any questions about it.
public static void Update()
{
foreach (var a in PhysicsEntities)
{
foreach (var b in PhysicsEntities)
{
if (a.Equals(b) ||
!Collision.ResolveCollision(a, b, Detection) || b.Collided) continue;
while (Detection == Detection.BoundingBox ?
Collision.BoundingBox(a, b) :
Collision.PixelPerfect(a, b))
{
const float moveBy = .5F;
var moveX = a.Position.X > b.Position.X ? moveBy : -moveBy;
var moveY = a.Position.Y > b.Position.Y ? moveBy : -moveBy;
if (a.Movable)
{
a.Move(moveX, moveY);
a.Velocity.Scale(-1);
}
else if (b.Movable)
{
b.Move(moveX * -1, moveY * -1);
b.Velocity.Scale(-1);
}
}
a.Update();
b.Update();
a.Collided = a.Movable;
}
}
foreach (var entity in PhysicsEntities)
{
entity.Update(velocity: true);
entity.Collided = false;
}
}
This script makes a cube "stick" to whatever it collided with. The problem is that when it's going at relatively high or medium speeds (or when the device itself is slow), the cube tends to "get a bit inside" what it collided with and then stick to it. What changes do I have to make to fix this?
In order for this script to work, one GameObject must have bool _sticksToObjects = true; and the other bool _sticksToObjects = false;
I have tried turning the Rigidbody's Collision Detection mode to either Continuous or Continuous Dynamic
I think my script depends on frame rate. That may be where the problem lies.
Normal "Attach":
Abnormal "Attach":
Rigidbody _rigidBody;
Transform _meshTransform;
bool _sticksToObjects = true;
public Transform _stuckTo = null;
protected Vector3 _offset = Vector3.zero;
void Awake()
{
GameObject CubeMesh = GameObject.FindWithTag ("CubeMesh");
GameObject Cube = GameObject.FindWithTag ("Cube");
_rigidBody = Cube.GetComponent<Rigidbody> ();
_meshTransform = CubeMesh.GetComponent<Transform> ();
}
void Update()
{
if (_stuckTo != null)
{
transform.position = _stuckTo.position - _offset;
}
}
void OnCollisionEnter(Collision collision)
{
if (!_sticksToObjects) {
return;
}
_rigidBody.isKinematic = true;
// Get the approximate collision point and normal, as there
// may be multipled collision points
Vector3 contactPoint = Vector3.zero;
Vector3 contactNormal = Vector3.zero;
for (int i = 0; i < collision.contacts.Length; i++) {
contactPoint += collision.contacts [i].point;
contactNormal += collision.contacts [i].normal;
}
// Get the final, approximate, point and normal of collision
contactPoint /= collision.contacts.Length;
contactNormal /= collision.contacts.Length;
// Move object to the collision point
// This acts as setting the pivot point of the cube mesh to the collision point
transform.position = contactPoint;
// Adjust the local position of the cube so it is flush with the pivot point
Vector3 meshLocalPosition = Vector3.zero;
// Move the child so the side is at the collision point.
// A x local position of 0 means the child is centered on the parent,
// a value of 0.5 means it's to the right, and a value of -0.5 means it to the left
meshLocalPosition.x = (0.5f * contactNormal.x);
_meshTransform.localPosition = meshLocalPosition;
if (_stuckTo == null || _stuckTo != collision.gameObject.transform) {
_offset = collision.gameObject.transform.position - transform.position;
}
_stuckTo = collision.gameObject.transform;
}
Here are some screenshots of the Unity editor:
This is a well-known category of problem in game engineering and you'll be pleased to know the solution is relatively simple. You'll be pleased to hear there are similar, but much more complicated, problems that are actually solved in the same way. I'll try to explain.
Now here's the thing. It's quite often that the following question comes up...
So I'm working on GTA. I have a humanoid, H, running around. She approaches vehicle V. She opens the door and gets in and drives off. After that everything goes to hell in Mecanim and all the code stops working. What to do?
Surprisingly, the way that is done in games is:
Surprisingly: you actually swap to totally different models at that point!!!!!
You have H and V in the game. But then you have an animation (say) for H climbing in to V. But then, you literally destroy the game objects of H and V, and you Instantiate (or just awake) a new, totally different, game object, which is D ("a car being driven around by a lady").
(If you think about it, you can see that when you do this, you carefully adjust all the stuff in D, so that it matches what was "just then happening" in the frame, in relation to both H and V. So for example, literally, you copy the transform, twist etc of the car V, to the new car-inside-D, if lady H has the SmearedMakeupEffect, you put the same SmearedMakeupEffect on the lady-within-D, you position all the bones identically, and so on.)
Another simple example of this is, you often get people asking, "my character C gets killed and I want it to become a ragdoll, how to?" In fact you just swap to a totally new game object you have all set up for that passage of the game. Indeed, if you have a character A ("Arnie") in a game, it's normal that you have 4 or 5 "different As" sitting offside the stage, so, there's "ragdoll A", "A who can dance" "A with weapon". And indeed many of these are combos, you know "A on the horse" "A in the car" and so on.
So interestingly, the "real" solution here is,
once they become a new connected thing, destroy them both and swap to a new game object altogether!
if you have made games "until you are blue in the face" from making games, this is just what you would do as a matter of course. Even though its' a simple situation, it's just easier in the long run. After all, consider all the stuff you have to do when this happens:
make hitting object child of the other
turn off physics on the child
change the way your physics works for the whole thing
turn off or change the collider on the hitting object, perhaps making it part of the overall object
you'll likely have some sort of new "separation" physics where it can be knocked-off - you'd have to turn all that on
likely change minor issues like sound effects, colors etc
As you can see it's a huge chore doing all this stuff, and indeed it's one of those things it's just "easier to do properly" and change to a new model.
All that being said, I know you want a Quick Script Solution you can Paste In :) Here it is...
Step 0, You'll create "YourScript" which goes on the "main" cube. it will "catch" another cube moving around.
YourScript will look basically like this ...
[System.NonSerialized] public bool isConnectedNow;
void OnCollisionEnter(Collision collision)
GameObject theThingWeCaught = collision.gameObject
Debug.Log("We caught this thing .. " + theThingWeCaught.name)
// make it a child of us......
theThingWeCaught.transform.parent = transform
theThingWeCaught ... set kinematic
theThingWeCaught ... probably disable the rigidbody
theThingWeCaught ... probably disable the collider
isConnectedNow = true;
That's really all you have to do.
Step 1, YOUR script must have a public bool like this
[System.NonSerialized] public bool isConnectedNow;
Step 2, Here's MyScript which goes on the hitting cube, first we'll unit-test that your isConnectedNow bool is working
public Class MyScript:MonoBehaviour // attach to the "child" cube
{
public float correctXDistance;
public float correctYDistance;
public Transform bigCube;
public YourScript yourScript;
void Update()
{
string message = yourScript.isConnectedNow ? "free" : "stuck";
Debug.Log("I am " + message);
}
}
attach, debug, and run. Make the little cube stick and unstick from the big cube .. Watch the console. it works? So add this to MyScript
private void DistanceCorrectionX()
{
float xDistance = bigCube.position.x - transform.position.x;
float xSign = Mathf.Sign(xDistance);
float xDelta = Mathf.Abs(xDistance);
float closenessPercentage = (xDelta/correctXDistance)*100f;
if ( closenessPercentage<90f || closenessPercentage>110f)
{
// they are not close enough to quantize on this axis
// this comes in to play when you have multiple axes
return; // do nothing.
}
float xShouldBe = bigCube.position.x + xSign * correctXDistance;
Vector3 p = transform;
p.x = xShouldBe; // be careful it's .y, .z etc for other axes
transform.position = p;
}
for now call that in Update() in MyScript like this
void Update()
{
Debug.Log("I am " yourScript.isConnectedNow ? "free" : "stuck");
if (yourScript.isConnectedNow) DistanceCorrectionX();
}
Now actually Play and make it stick. Now, since it's running in Update simply while Play look at the Inspector for MyScript and adjust the value of correctXDistance to get the exact look you want. When yo have decided on a value, unPlay and put that in as the final value you wish.
Next, in DistanceCorrectionX simply duplicate all the code and do it again for the Y axis DistanceCorrectionX. If you also do Z, do that.
Finally. Note you will have a lot of messy code, like this...
void Update()
{
// handle all the DistanceCorrectionX etc as seen above.
if (yourScript.isConnectedNow)
{
.. turn off the collider on me
}
else
{
.. turn on the collider on me
}
}
and so on, there's "many little things" you'll need to do.
Don't forget also, overwhelmingly you may want to make the hitting object a child of the big object, depending on your situation. (Then of course they would move around together as a unit.)
Note that in the positioning code above I just showed it as position, not local position, for pedagogic clarity. If you want to do them flinging around, and spinning and so on, you'd make the hitting object a child of the other and you would use localPosition in the same way. Enjoy.
One possible way that comes to my mind is:
Inside of the "collision enter" check the distance between these objects and move the one that should stick to the other one a bit away.
As you see in the picture the distance between A and B should be equal to the sum of the widths divided by 2 (with a small threshold of course).
If the distance is less than the sum of the widths / 2 then you have an abnormal "attach" and you have to move one of the objects away. Its not really difficult to accomplish that.
So basically I have an enemy that, once you get close enough, will chase you around and try to attack you. Once you get far enough away, it will run back to where it was originally.
The issue I'm having is that once it runs back to where its starting point is, but doesn't quite hit the point, and keeps falling over and trying to get to that exact point..
Here's what I have for code for that section:
if (inAggroRange()) {
//In aggro range
if (!inAttackRange()) {
chase();
} else {
animation.CrossFade(animationAttack.name);
attack();
if (animation[animationAttack.name].time > 0.9 * animation[animationAttack.name].length) {
impacted = false;
}
}
} else if (!inAggroRange()){
//Go back to home position
if (!(transform.position == enemyHomePosition)) {
animation.CrossFade(animationRun.name);
transform.LookAt(enemyHomePosition);
controller.SimpleMove(transform.forward * speed);
} else {
//Enemy is at home position
animation.CrossFade(animationIdle.name);
}
}
just use
private readonly float maxDistanceToHomePosition = 0.3f; // change this value as you like
...
if((transform.position - enemyHomePosition).magnitude > maxDistanceToHomePosition)
{
...
}
...
the way you are doing it, the enemy is trying to reach one specific point but without a megaton of luck it will always be just a bit over, then turn around and try again. Much like a golf player trying to hit the whole but alyways hitting to hard.
edit: also think about using != for 'not equal', in my opinion thats a bit easier to read