RotateFlipType helper functions? - c#

I've repeatedly found myself wanting to deal with the RotateFlipType in a more modular manner.
For example, at any given time I want to store a single RotateFlipType enum, while giving the user access to buttons to allow flipping and rotating one click at a time. I need to store this later when I need to apply the RotateFlipType to the data.
So basically I need to store everything seperately across multiple variables. Or I need to have logic such as:
private RotateFlipType ApplyHorizontalFlip(RotateFlipType oldFlip)
{
switch (oldFlip)
{
case RotateFlipType.Rotate180FlipNone:
return RotateFlipType.RotateNoneFlipNone;
case RotateFlipType.Rotate180FlipX:
return RotateFlipType.RotateNoneFlipX;
case RotateFlipType.Rotate180FlipXY:
return RotateFlipType.RotateNoneFlipXY;
case RotateFlipType.Rotate180FlipY:
return RotateFlipType.RotateNoneFlipY;
case RotateFlipType.Rotate270FlipNone:
return RotateFlipType.Rotate90FlipNone;
// etc...
}
}
Are there any helper methods build into the framework, or that anyone knows of? Basically to take any existing RotateFlipType and modify it by rotating or flipping to give a new value.

My approach is to store flip and rotation separately until the moment of rendering. I uses RotateFlipType for the flip value, and a number for the degree. I use these methods to combine them into the appropriate RotateFlipType value.
I'd suggest using your own enum instead of RotateFlipType as I've done here, especially if this is exposed as an external API.
private double normalizeTo90Intervals(double d){
d = d % 360; //Coalesce multiples
if (d < 0) d += 360; //Force positive
//Use manual rounding
if (d >= 315 && d < 360) return 0;
if (d >= 0 && d < 45) return 0;
if (d >=45 && d < 135) return 90;
if (d >= 135 && d < 225) return 180;
if (d >= 225 && d < 315) return 270;
return 0; //to make compiler happy
}
private RotateFlipType combineFlipAndRotate(RotateFlipType flip, double angle) {
angle = normalizeTo90Intervals(angle);
if (flip == 0) return (RotateFlipType)(int)(angle / 90);
else if (flip == (RotateFlipType)4) return (RotateFlipType)(int)(4 + (angle / 90));
else if (flip == (RotateFlipType)6) {
if (angle == 0) return (RotateFlipType)6;
if (angle == 90) return (RotateFlipType)7;
if (angle == 180) return (RotateFlipType)4;
if (angle == 270) return (RotateFlipType)5;
} else if (flip == (RotateFlipType)2) {
if (angle == 0) return (RotateFlipType)2;
if (angle == 90) return (RotateFlipType)3;
if (angle == 180) return (RotateFlipType)0;
if (angle == 270) return (RotateFlipType)1;
}
throw new ArgumentException("Valid flip values are RotateNoneFlipNone, RotateNoneFlipX, RotateNoneFlipY, and RotateNoneFlipXY. Rotation must be specified with Rotate or srcRotate instead. Received: " + flip.ToString());
}
FYI, have you heard of http://imageresizing.net/?

Related

I need help on the math for detecting the "side" of collision on a rotating sprite

So I am making a 2D space shmup that handles combat in a naval way. So you shoot out the broadsides of the ship, your shields and hull are divided into 4 sections: Forward, Starboard, Port, and Rear. I am not the greatest with math, but I managed to find a script that detects the side of my polygon collider that was hit by say a collision or projectile. That all works great.
The problem is my sprite rotates to steer in 2D space. So when I collide with something say for example with the nose of my ship, if my ship's nose is up then the collision is detected properly. But if the ship is rotated and the nose is now on the left and I collide with something on the nose, the script will detect the nose collision as a port side collision instead. Could somebody help me with correcting the math to account for my ship's rotation?
Collision2DExtension.cs
using UnityEngine;
namespace PixelsoftGames
{
public static class Collision2DExtensions
{
public static Collision2DSideType GetContactSide(Vector2 max, Vector2 center, Vector2 contact)
{
Collision2DSideType side = Collision2DSideType.None;
float diagonalAngle = Mathf.Atan2(max.y - center.y, max.x - center.x) * 180 / Mathf.PI;
float contactAngle = Mathf.Atan2(contact.y - center.y, contact.x - center.x) * 180 / Mathf.PI;
if (contactAngle < 0)
{
contactAngle = 360 + contactAngle;
}
if (diagonalAngle < 0)
{
diagonalAngle = 360 + diagonalAngle;
}
if (
((contactAngle >= 360 - diagonalAngle) && (contactAngle <= 360)) ||
((contactAngle <= diagonalAngle) && (contactAngle >= 0))
)
{
side = Collision2DSideType.Starboard;
}
else if (
((contactAngle >= 180 - diagonalAngle) && (contactAngle <= 180)) ||
((contactAngle >= 180) && (contactAngle <= 180 + diagonalAngle))
)
{
side = Collision2DSideType.Port;
}
else if (
((contactAngle >= diagonalAngle) && (contactAngle <= 90)) ||
((contactAngle >= 90) && (contactAngle <= 180 - diagonalAngle))
)
{
side = Collision2DSideType.Forward;
}
else if (
((contactAngle >= 180 + diagonalAngle) && (contactAngle <= 270)) ||
((contactAngle >= 270) && (contactAngle <= 360 - diagonalAngle))
)
{
side = Collision2DSideType.Rear;
}
return side.Opposite();
}
static bool ranOnce = false;
public static Collision2DSideType GetContactSide(this Collision2D collision)
{
Vector2 max = collision.collider.bounds.max;
Vector2 center = collision.collider.bounds.center;
Vector2 contact = collision.GetContact(0).point;
if (!ranOnce)
{
ranOnce = true;
Debug.Log("Max: " + max);
Debug.Log("Center: " + center);
Debug.Log("Contact: " + contact);
}
return GetContactSide(max, center, contact);
}
}
}
Collision2DSideTypeExtensions.cs
namespace PixelsoftGames
{
public static class Collision2DSideTypeExtensions
{
public static Collision2DSideType Opposite(this Collision2DSideType sideType)
{
Collision2DSideType opposite;
if (sideType == Collision2DSideType.Port)
{
opposite = Collision2DSideType.Starboard;
}
else if (sideType == Collision2DSideType.Starboard)
{
opposite = Collision2DSideType.Port;
}
else if (sideType == Collision2DSideType.Forward)
{
opposite = Collision2DSideType.Rear;
}
else if (sideType == Collision2DSideType.Rear)
{
opposite = Collision2DSideType.Forward;
}
else
{
opposite = Collision2DSideType.None;
}
return opposite;
}
}
}
Collision2DSideType
public enum Collision2DSideType { None, Port, Starboard, Forward, Rear }
In the method GetContactSide you never get the rotation of your sprite, it is like your sprite angle is always 0
One solution for this is to add as a parameter the angle of your sprite to the method and add that angle to the the condition to determine wich side of the sprite it is
It can look like that :
public static class Collision2DExtensions
{
public static Collision2DSideType GetContactSide(Vector2 max, Vector2 center, Vector2 contact, float angle)
{
...
if (
((contactAngle >= (360 - diagonalAngle) + angle) && (contactAngle <= 360 + angle)) ||
((contactAngle <= diagonalAngle + angle) && (contactAngle >= 0 + angle))
)
...
`
I think you should do that for each of these conditions

Change animation based on mouse screen position in Unity 2D

I am developing an Isometric 2D game in Unity, using C# scripts. The character will be able to run in 8 different orientations.
I am trying to trigger a running animation depending on the mouse position.
My script is working fine but I don't think is the best way to face this problem.
First of all, I have an enum with the possible orientations:
public enum Orientations {N,NE,E,SE,S,SW,W,NW,NONE}
I wrote a method that returns an Orientations value based in a movement. This is because I want to trigger an animation based on the movement, so the Character will always be looking at the direction of the movement:
public static Orientations GetOrientation(Vector2 movement)
{
if (movement.x == 0 && movement.y == 1)
{
return Orientations.N;
}
else if (movement.x == 1 && movement.y == 0)
{
return Orientations.E;
}
else if (movement.x == 0 && movement.y == -1)
{
return Orientations.S;
}
else if (movement.x == -1 && movement.y == 0)
{
return Orientations.W;
}
else if (movement.x == -1 && movement.y == 1)
{
return Orientations.NW;
}
else if (movement.x == 1 && movement.y == 1)
{
return Orientations.NE;
}
else if (movement.x == -1 && movement.y == -1)
{
return Orientations.SW;
}
else if (movement.x == 1 && movement.y == -1)
{
return Orientations.SE;
}
return Orientations.NONE;
}
Next, I get the mouse angle between the character and the screen.
public static float GetMousePosition(Transform transform)
{
float cameraDistance = Camera.main.transform.position.y - transform.position.y;
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, cameraDistance));
float angleRadius = Mathf.Atan2(mousePosition.y - transform.position.y, mousePosition.x - transform.position.x);
float angle = (180 / Mathf.PI) * angleRadius;
angle = (angle < 0) ? angle + 360 : angle;
return angle;
}
Then, I transform the angle in a Vector2, so I am able to switch between triggering animations by the character movement and mouse position:
public static Vector2 AngleToVectorDirection(Transform transform)
{
Vector2 direction = new Vector2(0,0);
float angle = GetMousePosition(transform);
if(angle >= 67.5 && angle < 112.5)
{
direction = new Vector2(0,1);
}
else if (angle >= 112.5 && angle < 157.5)
{
direction = new Vector2(-1,1);
}
else if (angle >= 157.5 && angle < 202.5)
{
direction = new Vector2(-1, 0);
}
else if (angle >= 202.5 && angle < 247.5)
{
direction = new Vector2(-1, -1);
}
else if (angle >= 247.5 && angle < 292.5)
{
direction = new Vector2(0, -1);
}
else if (angle >= 292.5 && angle < 337.5)
{
direction = new Vector2(1, -1);
}
else if (angle >= 337.5 || angle < 22.5)
{
direction = new Vector2(1, 0);
}
else if (angle >= 22.5 && angle < 67.5)
{
direction = new Vector2(1, 1);
}
return direction;
}
To finish, I return the Orientation as I mentioned:
public static Orientations GetOrientationByMovement(Transform transform, Vector2 movement)
{
Vector2 orientation;
if (!Input.GetButton("Fire1"))
{
orientation = movement;
}
else
{
orientation = AngleToVectorDirection(transform);
}
return GetOrientation(orientation);
}
This Orientation is received by an AnimationController script that triggers the animation.
I can not simply rotate the character, or flip sprite, or something like that because it is animation based.
Here you are doing the work twice. So in an optimisation point of view that not ideal but in a design point of view that can be good to separate the works. The question is where does the movement parameter come from in the GetOrientationByMovement method? can you use instead the orientation enum? if yes then that simplifies your code greatly!
You end up with :
public static Orientations AngleToVectorDirection(Transform transform)
{
float angle = GetMousePosition(transform);
if(angle >= 67.5 && angle < 112.5)
{
return Orientations.N;
}
else if (angle >= 112.5 && angle < 157.5)
{
return Orientations.NW;
}
else if (angle >= 157.5 && angle < 202.5)
{
return Orientations.W;
}
else if (angle >= 202.5 && angle < 247.5)
{
return Orientations.SW;
}
else if (angle >= 247.5 && angle < 292.5)
{
return Orientations.S;
}
else if (angle >= 292.5 && angle < 337.5)
{
return Orientations.SE;
}
else if (angle >= 337.5 || angle < 22.5)
{
return Orientations.E;
}
else if (angle >= 22.5 && angle < 67.5)
{
return Orientations.NE;
}
}
Also try to keep consistency in your code. if you use return value in the if statement do that for both function, in you use it outside do it everywhere.
Note that in your implementation of the AngleToVectorDirection you don't need to create a new vector in the beginning since you cover all the angle that can be.
Answering my own question:
With the just released unity's 3.8f1 patch, I've found in them demo project a way to trigger animations in a really simple way.
I am just using the code you can find in the official site:
https://blogs.unity3d.com/2019/03/18/isometric-2d-environments-with-tilemap/?_ga=2.120446600.1010886114.1552829987-288556513.1552829987
They use a IsometricCharacterRenderer script where use Animator.Play() passing as parameter a value of a string[], based on the player movement.
public static readonly string[] staticDirections = { "Static N", "Static NW", "Static W", "Static SW", "Static S", "Static SE", "Static E", "Static NE" };
public static readonly string[] runDirections = { "Run N", "Run NW", "Run W", "Run SW", "Run S", "Run SE", "Run E", "Run NE" };
public void SetDirection(Vector2 direction)
{
//use the Run states by default
string[] directionArray = null;
//measure the magnitude of the input.
if (direction.magnitude < .01f)
{
//if we are basically standing still, we'll use the Static states
//we won't be able to calculate a direction if the user isn't pressing one, anyway!
directionArray = staticDirections;
}
else
{
//we can calculate which direction we are going in
//use DirectionToIndex to get the index of the slice from the direction vector
//save the answer to lastDirection
directionArray = runDirections;
lastDirection = DirectionToIndex(direction, 8);
}
//tell the animator to play the requested state
animator.Play(directionArray[lastDirection]);
}
And to get the direction index, they convert the movement in an angle, just how I did, but in a smart way.
public static int DirectionToIndex(Vector2 dir, int sliceCount)
{
//get the normalized direction
Vector2 normDir = dir.normalized;
//calculate how many degrees one slice is
float step = 360f / sliceCount;
//calculate how many degress half a slice is.
//we need this to offset the pie, so that the North (UP) slice is aligned in the center
float halfstep = step / 2;
//get the angle from -180 to 180 of the direction vector relative to the Up vector.
//this will return the angle between dir and North.
float angle = Vector2.SignedAngle(Vector2.up, normDir);
//add the halfslice offset
angle += halfstep;
//if angle is negative, then let's make it positive by adding 360 to wrap it around.
if (angle < 0)
{
angle += 360;
}
//calculate the amount of steps required to reach this angle
float stepCount = angle / step;
//round it, and we have the answer!
return Mathf.FloorToInt(stepCount);
}

When creating a function in C# returning a value with many if/if else statements I get the error "not all code paths return a value"

I am making a game in Unity and need my character to face a certain angle based off of axis input values. After the rest of the calculations have been made to figure out an angle based off of a triangle, I have to add a value to the angle to make it face the desired way. For some reason I get this error "not all code paths return a value"
private float createDirection(float X, float Y, float theta)
{
if (X>0&&Y>0)
{
return theta;
}
else if (X>0&&Y<0)
{
float new = 270.0;
return theta + new;
}
else if (X<0&&Y>0)
{
float new = 90;
return theta + new;
}
else if (X<0&&Y<0)
{
float new = 180;
return theta + new;
}
}
There may be a change for all the specified conditions are false; in such scenario what will be the return value? what ever it be you have to specify them as default return to make sure that the function have some return value in all cases: So the function will be like the following:
private float createDirection(float X, float Y, float theta)
{
if (X > 0 && Y > 0)
{
return theta;
}
else if (X > 0 && Y < 0)
{
float _new = 270.0F;
return theta + _new;
}
else if (X < 0 && Y > 0)
{
float _new = 90;
return theta + _new;
}
else if (X < 0 && Y < 0)
{
float _new = 180;
return theta + _new;
}
return 0.0F; // default Return
}
And one more thing: You cannot use new as a variable since it is a reserved word. so i have changed them as _new in the code please note:
private float createDirection(float X, float Y, float theta)
{
float t;
if (X > 0 && Y > 0)
{
t = theta;
}
else if (X > 0 && Y < 0)
{
float new = 270.0;
t = theta + new;
}
else if (X < 0 && Y > 0)
{
float new = 90;
t = theta + new;
}
else if (X < 0 && Y < 0)
{
float new = 180;
t = theta + new;
}
return t;
}
You don't return a value when X or Y == 0.. (which isn't obvious either because you are using floats)
After the last else-block you can return a default value, OR, if that is not a code path which should ever be hit, you could throw an exception, i.e:
throw new ArgumentOutOfRangeException("X and Y must be greater or lesser than zero")

Doom-like angle based sprite changing

So, i'm trying to make a first person game that used the same sprite mechanics as games like Doom, Duke Nukem and etc.
So far, i can identify the angle I'm at in relation to static objects, but not to rotating ones. I have some "enemies" that change rotation and start following me, but calculating the tangent angle (Mathf.Atan2) doesn't take the enemy's rotation in consideration.
Here's the code i'm using so far, which works perfectly for objects that dont rotate:
int GetAngleIndex()
{
var dir = cam.transform.position - transform.parent.forward;
var enemyAngle = Mathf.Atan2(dir.z, dir.x) * Mathf.Rad2Deg;
if (enemyAngle < 0.0f)
enemyAngle += 360;
Debug.Log("Angle from the player is: " + enemyAngle);
if (enemyAngle >= 292.5f && enemyAngle < 337.5f)
return 8;
else if (enemyAngle >= 22.5f && enemyAngle < 67.5f)
return 2;
else if (enemyAngle >= 67.5f && enemyAngle < 112.5f)
return 3;
else if (enemyAngle >= 112.5f && enemyAngle < 157.5f)
return 4;
else if (enemyAngle >= 157.5f && enemyAngle < 202.5f)
return 5;
else if (enemyAngle >= 202.5f && enemyAngle < 247.5f)
return 6;
else if (enemyAngle >= 247.5f && enemyAngle < 292.5f)
return 7;
else if (enemyAngle >= 337.5f || enemyAngle < 22.5f)
return 1;
else return 0;
}
I've searched for hours and I can't find a solution to this :(
You say your [only] problem is that it doesn't take their rotation into account - I'm guessing that means that you're not having trouble billboarding your sprites, and your rotation works when they're facing forward. To that end:
The dot product of vectors a and b is equal to cos(theta)*magnitude(a)*magnitude(b). So if a is the vector from the camera to the object:
var a = cam.transform.position - transform.parent.position
and b is the object's forward:
var b = transform.parent.forward
and we know that a and b both have magnitude 1
a.Normalize();
//b is already normalized
then we know that this is equal to cos(theta), where theta is the angle between them.
var theta = Mathf.Acos(Vector3.Dot(a, b)) * Mathf.Rad2Deg;
However. Theta is the shortest necessary angle - so it will be from 0 to 180. Given your switch table up there, we know that when we're hoping to go around the wrong way, we'll be in the wrong position. so to fix that:
if (a.x * a.z < 0)
theta = 360.0f - theta;
then we just plug it in and go. Here's the full file in my project:
using UnityEngine;
public class spriteAngler : MonoBehaviour
{
public Transform toFace;
public SpriteRenderer toManipulate;
public Sprite[] mySprites;
private float theta;
private Vector3 a;
void Update()
{
toManipulate.sprite = mySprites[GetAngleIndex()];
}
int GetAngleIndex()
{
a = toFace.position - transform.position;
a.Normalize();
var b = transform.forward;
theta = Mathf.Acos(Vector3.Dot(a, b)) * Mathf.Rad2Deg;
if (a.x * a.z < 0)
theta = 360.0f - theta;
if (theta >= 292.5f && theta < 337.5f)
return 7;
else if (theta >= 22.5f && theta < 67.5f)
return 1;
else if (theta >= 67.5f && theta < 112.5f)
return 2;
else if (theta >= 112.5f && theta < 157.5f)
return 3;
else if (theta >= 157.5f && theta < 202.5f)
return 4;
else if (theta >= 202.5f && theta < 247.5f)
return 5;
else if (theta >= 247.5f && theta < 292.5f)
return 6;
else if (theta >= 337.5f || theta < 22.5f)
return 0;
else return 0;
}
private Rect guiPos = new Rect(0, 0, 720, 30);
void OnGUI()
{
GUI.Label(guiPos, "Angle from the Player is: " + theta + " and forward=" + transform.forward + " and vectorToTarget=" + a);
}
}
and if that needs a little more context, here's my project: https://github.com/AdamRGrey/22623013
I'd recommend hitting play but watching the scene window instead of the game window.
Maybe I'm not understanding the exact effect you're trying to create here (And if so please provide more information such as screenshots in your post), but you should be able to simply use Transform.LookAt(). These are typically called Billboard Sprites.
Example:
Transform.LookAt(Camera.main.transform.position, Vector3.up)
I guess what you are mentioning is the concept of Billboards.
Here is a sample in unity wiki for creating Billboards that always face Camera, go ahead and give it a try.
http://wiki.unity3d.com/index.php?title=CameraFacingBillboard

C# Collision test of a ship and asteriod [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
We are trying to to do a collision detection for the ship and asteroid.
If success than it should detect the collision before N turns.
However it is confused between angle 350 and 15 and it is not really working.
Sometimes it is moving but sometime it is not moving at all.
On the other hand, it is not shooting at the right time as well.
I just want to ask how to make the collision detection working???
And how to solve the angle confusion problem?
// Get velocities of asteroid
Console.WriteLine("lol");
// IF equation is between -2 and -3
if (equation1a <= -2)
{
// Calculate no. turns till asteroid hits
float turns_till_hit = dx / vx;
// Calculate angle of asteroid
float asteroid_angle_rad = (float)Math.Atan(Math.Abs(dy / dx));
float asteroid_angle_deg = (float)(asteroid_angle_rad * 180 / Math.PI);
float asteroid_angle = 0;
// Calculate angle if asteroid is in certain positions
if (asteroid.Y > ship.Y && asteroid.X > ship.X)
{
asteroid_angle = asteroid_angle_deg;
}
else if (asteroid.Y < ship.Y && asteroid.X > ship.X)
{
asteroid_angle = (360 - asteroid_angle_deg);
}
else if (asteroid.Y < ship.Y && asteroid.X < ship.X)
{
asteroid_angle = (180 + asteroid_angle_deg);
}
else if (asteroid.Y > ship.Y && asteroid.X < ship.X)
{
asteroid_angle = (180 - asteroid_angle_deg);
}
// IF turns till asteroid hits are less than 35
if (turns_till_hit < 50)
{
float angle_between = 0;
// Calculate angle between if asteroid is in certain positions
if (asteroid.Y > ship.Y && asteroid.X > ship.X)
{
angle_between = ship_angle - asteroid_angle;
}
else if (asteroid.Y < ship.Y && asteroid.X > ship.X)
{
angle_between = (360 - Math.Abs(ship_angle - asteroid_angle));
}
else if (asteroid.Y < ship.Y && asteroid.X < ship.X)
{
angle_between = ship_angle - asteroid_angle;
}
else if (asteroid.Y > ship.Y && asteroid.X < ship.X)
{
angle_between = ship_angle - asteroid_angle;
}
// If angle less than 0, add 360
if (angle_between < 0)
{
//angle_between %= 360;
angle_between = Math.Abs(angle_between);
}
// Calculate no. of turns to face asteroid
float turns_to_face = angle_between / 25;
if (turns_to_face < turns_till_hit)
{
float ship_angle_left = ShipAngle(ship_angle, "leftKey", 1);
float ship_angle_right = ShipAngle(ship_angle, "rightKey", 1);
float angle_between_left = Math.Abs(ship_angle_left - asteroid_angle);
float angle_between_right = Math.Abs(ship_angle_right - asteroid_angle);
if (angle_between_left < angle_between_right)
{
leftKey = true;
}
else if (angle_between_right < angle_between_left)
{
rightKey = true;
}
}
if (angle_between > 0 && angle_between < 25)
{
spaceKey = true;
}
}
}
}
}
How to make the collision detection working?
Find a reproducible scenario that does not work correctly.
Work out by hand the correct calculations for that scenario.
Start your program in the debugger. Watch the program perform those calculations.
When you reach a calculation that does not match the correct calculation that you worked out by hand, that's where the bug is.
In particular, look for places where the comments do not match the code; those are likely to be wrong. For example, this bit:
// If angle less than 0, add 360
if (angle_between < 0)
{
angle_between = Math.Abs(angle_between);
}
The comment says one thing and the code does something completely different. It is the code that actually runs.
And a word of advice: do all your calculations in doubles. There is no reason to keep casting doubles down to floats. Your making your program slower and less accurate by doing so. Also consider doing all your calculations in radians rather than converting back and forth between radians and degrees.
Also I note that you have plenty of opportunities for divisions by zero in your code. Those are likely to be bugs.

Categories