Unity - Looking through the scope of a gun - unity3d

Right now I have 2 Cameras: the main camera displays the gun at its normal state and a second camera is attached to the gun (the gun is a child of the main camera) and when toggled it looks through the scope of the gun and increases the field of view.
Heres a visual for a better understanding:
Now if I were to just toggle the second camera on and turn the main camera off, this would work splendid, but it's not very ideal. You should only have 1 camera per scene.
So I want to Lerp the position of the camera to look through the scope and manually decrease the fieldofview. So I have written the following script:
[RequireComponent(typeof(Camera))]
public class Zoom : MonoBehaviour {
private Transform CameraTransform = null;
public Transform ZoomedTransform;
private bool zoomed = false;
void Start () {
CameraTransform = Camera.main.transform;
}
// Update is called once per frame
void Update () {
if (Input.GetKey (KeyCode.LeftShift))
{
CameraTransform.position = Vector3.Lerp (
CameraTransform.position,
CameraTransform.position + ZoomedTransform.position,
5f * Time.deltaTime
);
CameraTransform.Rotate(ZoomedTransform.rotation.eulerAngles);
}
}
}
The problem with this is that it doesn't work: when I hit the zoom button, the camera speeds through the scene at the speed of light and it's hard to tell exactly what is going on.
Could anyone give me some insight as to what I'm doing wrong? I think it is something to do with the parent-child relationship, but even when I've tried using static values, I cannot seem to replicate the correct solution.
Hierarchy:

(This answer operates under the assumption that ZoomedTransform is a relative transformation, and not the absolute position of the camera as suspected by 31eee384's answer.)
I think there are a couple issues with your code. I'll tackle them individually so they're easier to understand, but they both relate to the following line:
CameraTransform.position = Vector3.Lerp (CameraTransform.position, CameraTransform.position + ZoomedTransform.position, 5f * Time.deltaTime);
First, let's look at how you're using Vector3.Lerp(). For the third argument of Vector3.Lerp(), you're supplying 5f * Time.deltaTime. What exactly does this value work out to? Well, the standard framerate is about 60 FPS, so Time.deltaTime = ~1/60. Hence, 5f * Time.deltaTime = 5/60 = ~0.0833.
What is Vector3.Lerp() expecting for the third argument, though? According to the documentation, that third argument should be between 0 and 1, and determines whether the returned Vector3 should be closer to the first or second given Vector3. So yes, 5f * Time.deltaTime falls within this range, but no interpolation will occur - because it will always be around ~0.0833, rather than progressing from 0 to 1 (or 1 to 0). Each frame, you're basically always getting back cameraPos + zoomTransform * 0.0833.
The other notable problem is how you're updating the value of CameraTransform.position every frame, but then using that new (increased) value as an argument for Vector3.Lerp() the next frame. (This is a bit like doing int i = i + 1; in a loop.) This is the reason why your camera is flying across the map so fast. Here is what is happening each frame, using the hypothetical result of your Vector3.Lerp() that I calculated earlier (pseudocode):
// Frame 1
cameraPosFrame_1 = cameraPosFrame_0 + zoomTransform * 0.0833;
// Frame 2
cameraPosFrame_2 = cameraPosFrame_1 + zoomTransform * 0.0833;
// Frame 3
cameraPosFrame_3 = cameraPosFrame_2 + zoomTransform * 0.0833;
// etc...
Every frame, zoomTransform * 0.0833 gets added to the camera's position. Which ends up being a really, really fast, and non-stop increase in value - so your camera flies across the map.
One way to address these problems is to have variables that stores your camera's initial local position, zoom progress, and speed of zoom. This way, we never lose the original position of the camera, and we can both keep track of how far the zoom has progressed and when to stop it.
[RequireComponent(typeof(Camera))]
public class Zoom : MonoBehaviour {
private Transform CameraTransform = null;
public Transform ZoomedTransform;
private Vector3 startLocalPos;
private float zoomProgress = 0;
private float zoomLength = 2; // Number of seconds zoom will take
private bool zoomed = false;
void Start () {
CameraTransform = Camera.main.transform;
startLocalPos = CameraTransform.localPosition;
}
// Update is called once per frame
void Update () {
if (Input.GetKey (KeyCode.LeftShift))
{
zoomProgress += Time.deltaTime;
CameraTransform.localPosition = Vector3.Lerp (startLocalPos, startLocalPos + ZoomedTransform.position, zoomProgress / zoomLength);
CameraTransform.Rotate(ZoomedTransform.rotation.eulerAngles);
}
}
}
Hope this helps! Let me know if you have any questions. This answer does ramble a little, so I hope you don't have any trouble getting the important points from it.

Your lerp target is relative to the camera's current position, so it's constantly moving. This is the target you have:
CameraTransform.position + ZoomedTransform.position
This means that as your camera moves to get closer to this position, the camera's new position causes the destination to change. So your camera keeps moving forever.
Your destination should be ZoomedTransform.position. No addition is necessary because position is in world coordinates. (And when you actually need to convert between spaces, check out TransformPoint and similar methods.)

It has been a while since I have done anything in Unity, but I think it is processing the Lerp function at frame time and not at actual time. You will need to call it in another function that is not being processed at frame time.

Related

How to get current frame from Animated Tile/Tilemap.animationFrameRate in Unity

I am using tilemaps and animated tiles from the 2dExtras in unity.
My tiles have 6 frames, at speed=2f, and my tilemap frame rate is 2.
New tiles placed always start on frame 1 and then immediately jump to the current frame of the other tiles already placed, the tilemap is keeping every tile at the same pace, which is working as I want.
However I would like the newly placed tiles to start at the frame the others are currently on,(instead of placing a tile that jumps from frame 1 to frame 4) I would like the new tile to start on frame 4
I've found how to pick the frame I want to start on, however I am having trouble retrieving which frame the animation is currently on, so I was wondering how exactly can I access the current frame of animation of a given tilemap ( Or a given tile, I can create a dummy tile and just read the info out of it, how can I get the current frame of an animated tile? )
The animated tilemaps feature seems to lack the feature to retrieve this information, also when I try tilemap.getsprite it always returns the first frame of the sequence(does not return the sprite currently displayed), and there doesn't seem to be any method to poll info from tilemap.animationFrameRate.
I thought another method would be to set a clock and sync it to the rate of the animation but since I can't get the exact framerate duration the clock eventually goes out of sync.
Any help would be appreciated!
I found a way to solve this question. But it's not 100% insurance.
First of all, I used SuperTile2Unity. That doesn't seem to be the point.
private void LateUpdate()
{
// I use this variable to monitor the run time of the game
this.totalTime += Time.deltaTime;
}
private void func()
{
// ...
TileBase[] currentTiles = tilemap.GetTilesBlock(new BoundsInt(new Vector3Int(0, 0, 0), new Vector3Int(x, y, 1)));
Dictionary<string, Sprite> tempTiles = new Dictionary<string, Sprite>();
//I use SuperTiled2Unity. But it doesn't matter, the point is to find animated tile
foreach (SuperTiled2Unity.SuperTile tile in currentTiles)
{
if (tile == null)
{
continue;
}
if (tile.m_AnimationSprites.Length > 1 && !tempTiles.ContainsKey(tile.name))
{
// find animated tile current frame
// You can easily find that the way SuperTile2Unity is used to process animation is to generate a sprite array based on the time of each frame set by Tiled animation and the value of AnimationFrameRate parameter.
// The length of array is always n times of AnimationFrameRate. You can debug to find this.
tempTiles.Add(tile.name, tile.m_AnimationSprites[GetProbablyFrameIndex(tile.m_AnimationSprites.Length)]);
}
}
//...
}
private int GetProbablyFrameIndex(int totalFrame)
{
//According to the total running time and the total length of tile animation and AnimationFrameRate, the approximate frame index can be deduced.
int overFrameTime = (int)(totalTime * animationFrameRate);
return overFrameTime % totalFrame;
}
I have done some tests. At least in 30 minutes, there will be no deviation in animations, but there may be a critical value. If the critical time is exceeded, there may be errors. It depends on the size of AnimationFrameRate and the accumulation mode of totalTime. After all, we don't know when and how the unity deals with animatedTile.
You could try using implementation presented in [1] which looks as follows:
MyAnimator.GetCurrentAnimatorClipInfo(0)[0].clip.length * (MyAnimator.GetCurrentAnimatorStateInfo(0).normalizedTime % 1) * MyAnimator.GetCurrentAnimatorClipInfo(0)[0].clip.frameRate;
[1] https://gamedev.stackexchange.com/questions/165289/how-to-fetch-a-frame-number-from-animation-clip

Using Time.timeScale to pause causes my player to 'dash' when unpausing, how do I stop this?

I'm moving my player with Input.GetAxis() and AddForce().
When I unpause the game with Time.timeScale = 1f, the player dashes forward at a higher speed (past my speed limit) even with the movement keys unpressed.
How can I stop this from happening?
for restricting speed from exceeding max limit you can use 'normalized'
void FixedUpdate()
{
if(rigidbody.velocity.magnitude > maxSpeed){
rigidbody.velocity = rigidbody.velocity.normalized * maxSpeed;
}
}
also you can use Vector3.ClampMagnitude.
see this https://docs.unity3d.com/ScriptReference/Vector3.ClampMagnitude.html
to be more specific you can share the part of the script.
Sounds like the force is still being applied and added constantly while the game is paused.
You could record the amount of force being applied to the player right before you set Time.timeScale = 0 and when you set Time.timeScale = 1f you could set the force to the recorded force before you paused.

Unity2d Jumping like teleport

I am working on Unity2d Sprite but something is wrong: in my code when I press the jump button my character jump like teleport, it go very fast in the Up direction. The gravity is 9 and my Rigidbody2D mass is 1. My code is this:
if(isGrounded == true && moveH == 0) {
if (Input.GetKeyDown(KeyCode.J)) {
anim.SetBool("str_jump", true);
//rgBody.velocity = new Vector2(0,jumpHeight);
//rgBody.AddForce(Vector2.up * jumpHeight);
transform.position = new Vector2(transform.position.x,
transform.position.y * jumpHeight);
isGrounded = false;
}
}
If your jump is too high, you have to use a variable to modify the height of the jump. In your case, you are using the jumpHeight as the variable to modify the height of your jump.
Decreasing jumpHeight should solve your problem. Note that if jumpHeight is declared as public (public float jumpHeight;), you have to change the variable from the Editor or a function because it will not use the number assigned to it when it was declared. It will use whatever that is assigned to it from the Editor.
For example, when you have public float jumpHeight = 4; but the value is set to 0 in the editor, 0 will be used as the value not 4.
Also you have to use rgBody.AddForce(Vector2.up * jumpHeight);. Uncomment it and then remove the transform.position = new Vector2(transform.position.x,transform.position.y *jumpHeight); line of code.
If this does not solve your problem, then you have to update your question with your complete code.
For a nice jump you could also use rgBody.AddForce((Vector2.up * jumpHeight), ForceMode2D.Impulse); It simulates an impact on your object and you get a nice jump simulation.
Do not forget to declare the public float jumpHeigt = 4f; like "Programmer" said it before.
I hope it will help you.
Simply reducing the jumpHeight value will fix the initial issue of the jump being too high.
However if you want a more realistic 'jump' you may want to utilise Rigidbody2D.velocity simply having rgBody.velocity = new Vector2(rgBody.velocity.x, jumpHeight); should create a nice smooth jump.
If you use the Rigidbody2D.velocity instead of simply changing the transform means that you can alter values such as Gravity Scale and this will make a noticeable difference on the movement of your player. You can play around with different jumpHeight and Gravity Scale values to find what works best for your game.
Use transform.position '+=' instead of just '='. This way you're adding to it's position and not setting it to a certain number(that's why it's teleporting like that). Tho this method only suits horizontal movement in my opinion(if it's a sideview game). For jumping, using AddForce would be way better.

Flipping a child sprite's xscale in Spritekit using Swift

Alright I'm offically stumped. I've been able to figure out my problems myself in the past but I'm lost on this one.
I'm trying to flip a child sprite's xscale. I want the flip to occur when the child's x position is negative. With the code below, the sprite does flip, every time the sprite reaches negative position, it loops back and forth flipping until reaching positive again.
I've tried numerous variations, but this is the code in it's simplest form. Any help or alternatives would be appreciative.
override func update(currentTime: CFTimeInterval) {
/* Called before each frame is rendered */
let pin = childNodeWithName(Pin1) as SKSpriteNode!
let guyPoint = guy.convertPoint(guy.position, fromNode: pin)
if guyPoint.x <= 0 {
guy.xScale = -1
}
else {
guy.xScale = 1
}
}
So if it is constantly flipping it's probably an issue of coordinate space. So check to make sure your convertPoint method is accurately returning the point you want. I would use println() to see what's going on there.
Probably what's happening is you want to flip the xScale when the child node is negative in the scene space or something (i.e. when the child is off the screen) but instead guyPoint might be the position of the child in its parent's coordinate space (so relative to the parent).
Also try with and without the else{} part to see if that changes anything.

Drag sprite in cocos2d for the iPhone - with a max velocity

I'm trying to make a game where the user is supposed to drag a sprite up and down on the screen, avoiding incoming obstacles. The last answer here helped me to drag the sprite around on the screen, but I want to set a maximum speed the sprite can be moved (and hopefully with a natural-looking acceleration/deceleration), so it doesn't get too easy to avoid the objects.
Does anybody know how I can modify the code to achieve this, or is there another way to to it?
Thanks :)
You'll need to maintain a CGPoint destinationPosition variable which is the location of your finger and use an update loop to modify it's position:
-(void) update:(ccTime) dt
{
CGPoint currentPosition = draggableObject.position.x;
if (destination.x != currentPosition.x)
{
currentPosition.x += (destination.x - currentPosition.x) / 5.0f; // This 5.0f is how fast you want the object to move to it's destination
}
if (destination.y != currentPosition.y)
{
currentPosition.y += (destination.y - currentPosition.y) / 5.0f;
}
draggableObject.postion = currentPosition;
}
In the ifs, you might want to check if the objects are close to each other, rather than exactly the same number to allow for rounding errors.
You just need to have an if statement in whatever schedule updater you are using, like time, or touches, or whatever.
I'm presuming you have x/y velocities? Just inside your update statement, wherever your acceleration is -
if(acceleration.x > 20){
acceleration.x = 20;
}
if(acceleration.y > 20){
acceleration.y = 20;
}