I've been working on a top view game, where there're two virtual joysticks present to control a character. For the joysticks, I've used Spritekit-Joystick. The character shoots automatically. The right Joystick is used for changing direction, and I have implemented this function to move the character:
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
if (self.imageJoystick.velocity.x != 0 || self.imageJoystick.velocity.y != 0)
{
myCharacter.zRotation = self.imageJoystick.angularVelocity;
}
}
For the self.imageJoystick.angularVelocity, the Joystick library uses this code (the code is available from the link above, but just for the sake of making things easier and more readily available):
angularVelocity = -atan2(thumbNode.position.x - self.anchorPointInPoints.x, thumbNode.position.y - self.anchorPointInPoints.y);
There' a problem with the rotation. When I push the joystick (thumbnode) up, the rotation points to the right. I've used NSLog to get the value, and it shows around 0. When I push the joystic (thumbnode above) to the left, the character points upwards, and I get around 1.5 reading for angularVelocity. Pushing it down points the character to left and gives a -3 reading. Finally, when I push right, I point the character to South, and geta reading of around 1.5.
The same thing happens with the 'bullets' I shoot out. For now I'm using beams, which I found the answer in this [POST] on here2. I have added it below for your convenience (Not my answer, but I've changed it to be used in my game):
[[self childNodeWithName:#"RayBeam"]removeFromParent];
int x = myCharacter.position.x + 1000 * cos(myCharacter.zRotation);
int y = myCharacter.position.y + 1000 * sin(myCharacter.zRotation);
SKShapeNode* beam1 = [SKShapeNode node];
CGMutablePathRef pathToDraw = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, myCharacter.position.x, myCharacter.position.y);
CGPathAddLineToPoint(pathToDraw, NULL, x, y);
beam1.path = pathToDraw;
[beam1 setStrokeColor:[UIColor redColor]];
[beam1 setName:#"RayBeam"];
[self addChild:beam1];
The exact same thing happens. When I 1) push the joystick up, I shoot to right. 2) push the joystick to the left, I shoot Upward. 3) push downward, I shoot left, and 4) when I push the joystick to the right, I shoot downward.
I think I need to tweek the math and trigonometry equations, but maths isn't my forte, and I'm relatively new to this.
The image below is the image of the game (it's not completed at all), and you can see I'm pushing up but I'm pointing, and shooting to right.
Note: my sprite is also point to the right by default. I tried rotating all my sprites counter clockwise, and that works with the sprites, but not with the beam.
I really appreciate your help.
Thank you in advance.
If up is 0 radians then you should be calculating your beam's x and y with:
int x = myCharacter.position.x - 1000 * sin(myCharacter.zRotation);
int y = myCharacter.position.y + 1000 * cos(myCharacter.zRotation);
Related
Hi I have a question that I'm hoping someone can help me work through. I've asked elsewhere to no avail but it seems like a standard problem so I'm not sure why I haven't been getting answers.
Its basically setting up a zoom function that mirrors Google Maps zoom. Like, the camera zooms in/out onto where your mouse is. I know this probably gets asked a lot but I think Unity's new Input System changed things up a bit since the 4-6 year old questions that I've found in my own research.
In any case, I've set up an parent GameObject that holds all 2D sprites that will be in my scene and an orthographic camera. I can set the orthographic size through code to change to zoom, but its moving the camera to the proper place that I am having trouble with.
This was my 1st attempt:
public Zoom(float direction, Vector2 mousePosition) {
// zoom calcs
float rate 1 + direction * Time.deltaTime;
float targetOrtho = Mathf.MoveTowards(mainCam.orthographicSize, mainCam.orthoGraphicSize/rate, 0.1f);
// move calcs
mousePosition = mainCam.ScreenToWorldPoint(mousePosition);
Vector2 deltaPosition = previousPosition - mousePosition;
// move and zoom
transform.position += new Vector3(deltaPosition.x, deltaPosition.y, 0);
// zoomLevels are a generic struct that holds the max/min values.
SetZoomLevel(Mathf.Clamp(targetOrthoSize, zoomLevels.min, zoomLevels.max));
previousPosition = mousePosition;
}
This function gets called through my input controller, activated through Unity's Input System events. When the mouse wheel scrolls, the Zoom function is given a normalized value as direction (1 or -1) and the current mousePosition. When its finished its calculation, the mousePosition is stored in previousPosition.
The code actually works -- except it is extremely jittery. This, of course happens because there is no Time.deltaTime applied to the camera movement, nor is this in LateUpdate; both of which helps to smooth the movements. Except, in the former case, multiplying Time.deltaTime to new Vector3(deltaPosition.x, deltaPosition.y, 0) seems to cause the zoom occur at the camera's centre rather than the mouse position. When i put zoom into LateUpdate, it creates a cool but unwanted vibration effect when the camera moves.
So, after doing some thinking and reading, I thought it may be best to calculate the difference between the mouse position and the camera's center point, then multiply it by a scale factor, which is the camera's orthographic size * 2 (maybe...??). Hence my updated code here:
public void Zoom(float direction, Vector2 mousePosition)
{
// zoom
float rate = 1 + direction * Time.unscaledDeltaTime * zoomSpeed;
float orthoTarget = Mathf.MoveTowards(mainCam.orthographicSize, mainCam.orthographicSize * rate, maxZoomDelta);
SetZoomLevel(Mathf.Clamp(orthoTarget, zoomLevels.min, zoomLevels.max));
// movement
if (mainCam.orthographicSize < zoomLevels.max && mainCam.orthographicSize > zoomLevels.min)
{
mousePosition = mainCam.ScreenToWorldPoint(mousePosition);
Vector2 offset = (mousePosition - new Vector2(transform.position.x, transform.position.y)) / (mainCam.orthographicSize * 2);
// panPositions are the same generic struct holding min/max values
offset.x = Mathf.Clamp(offset.x, panPositions.min.x, panPositions.max.x);
offset.y = Mathf.Clamp(offset.y, panPositions.min.y, panPositions.max.y);
transform.position += new Vector3(offset.x, offset.y, 0) * Time.deltaTime;
}
}
This seems a little closer to what I'm trying to achieve but the camera still zooms in near its center point and zooms out on some point... I'm a bit lost as to what I am missing out here.
Is anyone able to help guide my thinking about what I need to do to create a smooth zoom in/out on the point where the mouse currently is? Much appreciated & thanks for reading through this.
Ok I figured it out for if anyone ever comes across the same problem. it is a standard problem that is easily solved once you know the math.
Basically, its a matter of scaling and translating the camera. You can do one or the other first - it does not matter; the outcome is the same. Imagine your screen looks like this:
The green box is your camera viewport, the arrow is your cursor. When you zoom in, the orthographic size gets smaller and shrinks around its anchor point (usually P1(0,0)). This is the scaling aspect of the problem and the following image explains it well:
So, now we want to move the camera position to the new position:
So how do we do this? Its just a matter of getting distance from the old camera position (P1(0, 0)) to the new camera position (P2(x,y)). Basically, we only want this:
My solution to find the length of the arrow in the picture above was to basically subtract the length of the cursor position from the old camera position (oldLength) from the length of the cursor position to the new camera position (newLength).
But how do you find newLength? Well, since we know the length will be scaled accordingly to the size of the camera viewport, newLength will be either oldLength / scaleFactor or oldLength * scaleFactor, depending on whether you want to zoom in or out, respectively. The scale factor can be whatever you want (zoom in/out by 2, 4, 1.4... whatever).
From there, its just a matter of subtracting newLength from oldLength and adding that difference from the current camera position. The psuedo code is below:
(Note that i changed 'newLength' to 'length' and 'oldLength' to 'scaledLength')
// make sure you're working in world space
mousePosition = camera.ScreenToWorldPoint(mousePosition);
length = mousePosition - currentCameraPosition;
scaledLength = length / scaleFactor // to zoom in, otherwise its length * scaleFactor
deltaLength = length - scaledLength;
// change position
cameraPosition = currentCameraPosition - deltaLength;
// do zoom
camera.orthographicSize /= scaleFactor // to zoom in, otherwise orthographic size *= scaleFactor
Works perfectly for me. Thanks to those who helped me in a discord coding community!
I'm new in Unity. I've got game with FPS mode, camera rotate with mouse's moves and sun is the directional light. I must write script where I check if sun is in player's field of view. I thought that I can calculate angle between 2 vectors and then I decide if sun is visible. First vector is:
var playerSun = light.transform.position - camera.transform.position;
But I have problem with the second one... I don't know which variable I should use, camera.transform.forward is ALWAYS (0,0,1)...
Can you help me? I'll be very grateful.
Vector3 direction = light.position - player.position;
float dot = Vector3.Dot(direction.normalized, player.forward);
if(dot > 0) { Debug.Log("Sun is on the front"); }
dot product returns 1 when two vectors are aligned, 0 when they are 90 degrees and -1 when they are opposite.
The value is in radians so if you need a 90 degrees FOV, it would be 45 degrees (since 90 is 45 left and right) and that is appr. 0.7f.
if(dot > 0.7f) { Debug.Log("Sun is withing 90 degrees");}
There are several ways to achieve that, but I suggest usage of Raycast.
I suppose sun is more than just a dot, it has some area visible to the player, so even if he does not see sun` center point, he still can see some part of its area. If so, I recommend to add a new script to sun object just to identify it programmatically. Then make sure that it has collider component attached with the size approximately equal to sun.
Then in your script in which you want to detect sun visibility to the player you can do something like that:
var ray = Camera.main.ScreenPointToRay(pos);
RaycastHit hit;
if (!Physics.Raycast(ray, out hit, 100))
return false; //sun or any other collider wasnt hit
var objHit = hit.collider.gameObject.GetComponent<Sun>();
So objHit != null means that player can see any part of area that sun has.
I have an object that rotates according to mouse position, but I want to clamp it so it doesn't get further or lower than certain value. Here is my code:
void LookAt () {
float distance = transform.position.z - Camera.main.transform.position.z;
Vector3 position = new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance);
position = Camera.main.ScreenToWorldPoint(position);
position.x = Mathf.Clamp(position.x, -70, 70);
position.z = Mathf.Clamp(position.z, -70, 70);
Vector3 target = new Vector3 (position.x, transform.position.y, position.z); // Use current object positin.y
transform.LookAt(target);
}
But unfortunately it doesn't work, it keeps rotating 360.
Edit:
This is a 3D top-down game, I have a tank and I want to rotate it's upper half. The code I wrote above works perfect for the job, but now I don't know how to limit it so the barrel( the part I'm rotating) always facing upwards where the enemies will come from. 70 or whatever are just random values I was testing, first I want to figure what exactly the proper code is, then determining the values is the easy part.
Actually, the problem is that you're clamping a position, not a rotation. You're having it look at a certain point, but limiting that point rather than the angle that it will need to rotate to meet it. You'll have to use trigonometry to calculate the angle it wants to point in (more specifically, the atan2 function), clamp that value to (-70, 70), and then apply that rotation to the object (using euler angles). Do you require further clarification on any of these steps?
Cheers.
P.S. Note that atan2 returns a value in radians, but your range and euler angles use degrees.
You limit your target position by 70 units in world space, which is usually a lot, but depends on your game scale. What I think you wanted to do is to limit mouse position by 70 pixels around the screen center. (Please, provide this remarks in the question itself, so we won't have to guess). However, because you used the same variable both for screen space and world space position of the target, you likely got confused and clamped the position after converting it to world space.
Also, you made the y coordinate of the target to be the same as the object. But this means that the object would have to rotate 360 degrees every time the target passed it. I assume that what you wanted to do instead is to assume that the target location is located on camera place.
void LookAt () {
var cursorPosition = new Vector3(Input.mousePosition.x, Input.mousePosition.y, camera.main.nearClipPlane);
cursorPosition.x = Mathf.Clamp(position.x, -70, 70);
cursorPosition.z = Mathf.Clamp(position.z, -70, 70);
var targetPosition = Camera.main.ScreenToWorldPoint(cursorPosition);
transform.LookAt(targetPosition);
}
Please, provide details about your reasoning and desired behavior when you ask to find errors in your code.
I'm new in Unity. I want to do that wheels (cylinders) in this car will turn around if I press "up" button on my keyboard.
This is the code I've written:
var forwardSpeed: float = 3;
function Start () {
}
function Update () {
var forwardMoveAmount = Input.GetAxis("Vertical")*forwardSpeed;
transform.Rotate(0, forwardMoveAmount, 0);
}
OK. Wheels are turning around, but my car is still at the same place. What should I do to move this car?
PS: can you explain me, why this cylinder rotates correctly, when I use Y axis? It should be z.
For physics based wheels, you probably want wheel colliders. Their use is pretty well documented here:
http://docs.unity3d.com/Documentation/Components/class-WheelCollider.html
You're applying the distance scalar to a rotate function, and not translating (Moving) the object.
transform.Rotate will rotate the object.
transform.Translate will move the object.
Pick an object on your desk. If you rotate it 90 degrees twice, it will be rotated 180 degrees but still be in the same place. Now imagine every time you rotate that object, you move it in the direction it's facing by a couple inches. After 4 cycles, the object will have completed a full circuit of 360 degrees.
To represent this in code:
var forwardSpeed: float = 3; // Tweak me
var turnAngle: float = 1; // Tweak me
function Start () {
}
function Update () {
// Rotate first
transform.Rotate(0, Vector3.right * turnAngle, 0);
// Move forward along the rotated axis
transform.Translate(0, Vector3.forward * forwardSpeed, 0);
}
You'll need to also explicitly move the car at the same time, using presumably transform.position, transform.Translate(), or something similar on the parent car object.
I would like to use Cocos2d on the iPhone to draw a 2D car and make it steer from left to right in a natural way.
Here is what I tried:
Calculate the angle of the wheels and just move it to the destination point where the wheels point to. But this creates a very unnatural feel. The car drifts half the time
After that I started some research on how to get a turning circle from a car, which meant that I needed a couple of constants like wheelbase and the width of the car.
After a lot of research, I created the following code:
float steerAngle = 30; // in degrees
float speed = 20;
float carWidth = 1.8f; // as in 1.8 meters
float wheelBase = 3.5f; // as in 3.5 meters
float x = (wheelBase / abs(tan(steerAngle)) + carWidth/ 2);
float wheelBaseHalf = wheelBase / 2;
float r = (float) sqrt(x * x + wheelBaseHalf * wheelBaseHalf);
float theta = speed * 1 / r;
if (steerAngle < 0.0f)
theta = theta * -1;
drawCircle(CGPointMake(carPosition.x - r, carPosition.y),
r, CC_DEGREES_TO_RADIANS(180), 50, NO);
The first couple of lines are my constants. carPosition is of the type CGPoint. After that I try to draw a circle which shows the turning circle of my car, but the circle it draws is far too small. I can just make my constants bigger, to make the circle bigger, but then I would still need to know how to move my sprite on this circle.
I tried following a .NET tutorial I found on the subject, but I can't really completely convert it because it uses Matrixes, which aren't supported by Cocoa.
Can someone give me a couple of pointers on how to start this? I have been looking for example code, but I can't find any.
EDIT After the comments given below
I corrected my constants, my wheelBase is now 50 (the sprite is 50px high), my carWidth is 30 (the sprite is 30px in width).
But now I have the problem, that when my car does it's first 'tick', the rotation is correct (and also the placement), but after that the calculations seem wrong.
The middle of the turning circle is moved instead of kept at it's original position. What I need (I think) is that at each angle of the car I need to recalculate the original centre of the turning circle. I would think this is easy, because I have the radius and the turning angle, but I can't seem to figure out how to keep the car moving in a nice circle.
Any more pointers?
You have the right idea. The constants are the problem in this case. You need to specify wheelBase and carWidth in units that match your view size. For example, if the image of your car on the screen has a wheel base of 30 pixels, you would use 30 for the WheelBase variable.
This explains why your on-screen circles are too small. Cocoa is trying to draw circles for a tiny little car which is only 1.8 pixels wide!
Now, for the matter of moving your car along the circle:
The theta variable you calculate in the code above is a rotational speed, which is what you would use to move the car around the center point of that circle:
Let's assume that your speed variable is in pixels per second, to make the calculations easier. With that assumption in place, you would simply execute the following code once every second:
// calculate the new position of the car
newCarPosition.x = (carPosition.x - r) + r*cos(theta);
newCarPosition.y = carPosition.y + r*sin(theta);
// rotate the car appropriately (pseudo-code)
[car rotateByAngle:theta];
Note: I'm not sure what the correct method is to rotate your car's image, so I just used rotateByAngle: to get the point across. I hope it helps!
update (after comments):
I hadn't thought about the center of the turning circle moving with the car. The original code doesn't take into account the angle that the car is already rotated to. I would change it as follows:
...
if (steerAngle < 0.0f)
theta = theta * -1;
// calculate the center of the turning circle,
// taking int account the rotation of the car
circleCenter.x = carPosition.x - r*cos(carAngle);
circleCenter.y = carPosition.y + r*sin(carAngle);
// draw the turning circle
drawCircle(circleCenter, r, CC_DEGREES_TO_RADIANS(180), 50, NO);
// calculate the new position of the car
newCarPosition.x = circleCenter.x + r*cos(theta);
newCarPosition.y = circleCenter.y + r*sin(theta);
// rotate the car appropriately (pseudo-code)
[car rotateByAngle:theta];
carAngle = carAngle + theta;
This should keep the center of the turning circle at the appropriate point, even if the car has been rotated.