As the title says, I'm trying to create a mechanic that decides what landing the third person controller should play. The player has a "HardLanding" and a "NormalLanding" animation. At first I tried achieving this with a timer, but this doesn't deliver the expected behaviour (When the player jumps forward it also plays the HardLanding animation, which is not what should happen). I'm using a character controller component, so there is no rigidbody or collider (Only the CharacterController and the script).
I have already setup a void that raycasts downwards, and this is currently being done when the player is not grounded. The problem is that the groundCheck is being updated every frame because it's in the update function. This means this ray is sent out every frame and gives different values. I think this ray should either only shoot once, so there is a single value that determines the height of the player, or that the ray that currently shoots out as long as the player is not grounded should be checked on its highest value and than I can do something like. if (rayheight number is higher than x) { play HardLanding} else {play NormalLanding}. The landing animation should start playing when the player is grounded again obviously.
Here is what I have gotton so far, which doens't work yet:
void CheckFallingDistance()
{
RaycastHit hitFall;
Vector3 bottom = controller.transform.position - new Vector3(0, controller.height / 2, 0);
if (Physics.Raycast(bottom, dirDown, out hitFall))
{
rayDistance = hitFall.distance;
}
Debug.DrawRay(bottom, dirDown * hitFall.distance, Color.cyan, 10f);
}
Below you can see my current groundcheck which still contains the timer to decide which landing animation should play. The timer part should be replaced with the raycast check for falling height.
if (GroundCheck())
{
velocityY = 0;
if (airTime > airTimeHandler)
{
//Debug.Log("Hard landing");
anim.SetBool("hardLanding", true);
anim.SetBool("onAir", false);
anim.SetBool("onAirIdle", false);
}
else
{
//Debug.Log("Normal landing");
anim.SetBool("hardLanding", false);
anim.SetBool("onAir", false);
anim.SetBool("onAirIdle", false);
}
airTime = 0f;
}
else
{
CheckFallingDistance();
if (animationSpeedPercent < 0.51f)
{
anim.SetBool("onAirIdle", true);
}
else
{
anim.SetBool("onAir", true);
}
anim.SetBool("hardLanding", false);
airTime += Time.deltaTime;
}
You know your player's absolute position (y component), what remains is finding out the gound height at this point. If your floor is at y=0 than this is trivial, but since you mention raycasting I am assuming its not.
I would assume you only need to measure height at the highest point, you can launch a Coroutine when you leave the ground (its best to avoid Update()), and check for vertical position in each frame within that coroutine. At the beggining phase of the jump the position will increase, but at some point current position will become lower than the position in the last frame. This is the frame when you shoot your raycast, and make a decision as to which animation you play. You can end your coroutine at this point (this is unless your ground is terribly uneven).
You could also measure player velocity before landing and decide based on that
Eventually I decided to ditch the raycast part and went for a check of the Y velocity of the charactercontroller component.
Related
Preamble:
I have a 3D side scroller style game in which the player flies along avoiding stuff, you know, side-scrollery things.
I'd like to add an effect (particle system) to the player when they get close (within a preset dangerZone, say 1.6 units (meters)) to the terrain, like a dusty dragging cloud under them sort of thing. I'm familiar with the particle system and raycasts but I don't know how to marry the concepts together to achieve what I'm after. The terrain undulates randomly and is not a flat surface, if that helps.
I'd also be hoping to make the particle system 'grow' the closer the player gets to the terrain if that makes sense. There is also speed to consider, so the closer to the ground and faster the player is should have an effect on the particle system.
My Thoughts:
I already have a score multiplier that uses a raycast to check the player's position from the ground/terrain and increases the closer they get.
void Update() {
force = speed *2;
RaycastHit hit;
Ray downRay = new Ray(transform.position, -Vector3.up);
if (Physics.Raycast(downRay, out hit, dangerZone)) {
var distanceToGround = hit.distance;
float hazardMultiplier = Mathf.Round( (transform.position.y - distanceToGround)*100 ) /100;
if (hit.collider.tag == "Terrain") {
playerData.scoreMulitplier = hazardMultiplier;
}
}
else {
playerData.scoreMulitplier = playerData.baseScoreMulitplier;
}
}
I'm thinking I can use the raycast I already have to instantiate a particle system on the terrain/at the raycast hit point but I'm not sure how exactly to go about this.
Any help is appreciated and thanks in advance.
You're on the right track. A couple things first before we dive into the solution:
Raycasts are calculated on the fixed frame (physics, FixedUpdate), not the visual frame (Update). While you may invoke a Raycast during Update, it won't be calculated until the next FixedFrame anyways. I'd recommend moving this to FixedUpdate to reduce the chance of doubled logic (2 simultaneous raycasts) or skipped logic (no raycasts).
You can set your particle system's scaling mode to the hierarchy, and scale using the transform. Alternatively, you can set the startSize of the particleSystem's main attributes.. Since you want to change the size of the particleSystem to change fairly frequently, I would recommend changing the scaling mode to hierarchy and just modifying the transform of the object it is attached to.
[SerializeField]
ParticleSystem ps;
[SerializeField]
float dangerZone = 1.6f;
[SerializeField]
float maxParticleSize = 2.0f; //How much you want particles to scale up based on closeness to terrain
void FixedUpdate() {
RaycastHit hit;
Ray downRay = new Ray(transform.position, -Vector3.up);
//Consider adding a layerMask parameter here that only interacts with the "terrain" layer. This will save you physics computing power, and remove the need for tag checking every frame (which is not efficient).
if (Physics.Raycast(downRay, out hit, dangerZone)) {
var distanceToGround = hit.distance;
float hazardMultiplier = Mathf.Round( (transform.position.y - distanceToGround)*100 ) /100; //This doesn't make too much sense to me, so you may want to revisit this. Since this isn't scoped for this question we can ignore it.
//If you use a layerMask, then you wouldn't ever need to check
if (hit.collider.tag == "Terrain") {
playerData.scoreMulitplier = hazardMultiplier;
float particleScale = 1 + (dangerZone - hit.distance) / dangerZone;
ps.transform.localScale = (particleScale, particleScale, particleScale);
if(ps.isStopped)
ps.Play();
} else //In scenarios where the raycast hits a non-terrain target, you'll need to turn off particles. If you use a layermask this wouldn't be needed.
{
if(ps.isPlaying)
ps.Stop();
}
}
else {
if(ps.isPlaying)
ps.Stop();
playerData.scoreMulitplier = playerData.baseScoreMulitplier;
}
}
Im new to unity and this is my first game but my player is jumping infinitely and I've watched a lot of tutorials and still dont know how to fix it.
heres my code
public float moveSpeed = 5f;
void Update()
{
Jump();
Vector3 movement = new Vector3(Input.GetAxis("Horizontal"), 0f, 0f);
transform.position += movement * Time.deltaTime * moveSpeed;
Vector3 characterScale = transform.localScale;
if (Input.GetAxis("Horizontal") < 0)
{
characterScale.x = 1;
}
if (Input.GetAxis("Horizontal") > 0)
{
characterScale.x = -1;
}
transform.localScale = characterScale;
}
void Jump()
{
if (Input.GetButtonDown("Jump"))
{
gameObject.GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, 15f), ForceMode2D.Impulse);
}
}
}
There are a couple of things you have there. First you have Input.GetButtonDown("Jump") This means while the player is holding the button down it will execute that script inside the if statement, which applies a force. So this will run every frame and every frame while the player is holding down the button it will apply that force. You can try to do Input.GetButtonUp("Jump") which will be true when the player lets go of the button, then it will apply that force. Now you can keep the GetButtonDown its no problem if thats the feel you are going foor.
But the real problem and the second this is, you need to check if the player is touching the ground or not. If he is touching the ground then you can apply that force. If he is not touching the ground then dont apply the force.
There are couple of ways to go about this, the easiest way is to create a new Layer and call it Ground or something.
You click that drop down and click on Add layer .. then you can add a layer, then go back to ground gameobject and assign that layer to that. Now am assuming that the ground has a collider on it so the player doesnt go through it.
After that you need a reference to the player collider. In the Start() method you can add this:
private Collider2D myCollider;
void Start()
{
// This will get a reference to the collider 2d on the player
myCollider = GetComponent<Collider2D>();
}
void Update()
{
.
.
.
// This means the player is touching the ground layer.
if (myCollider.IsTouchingLayers(LayerMask.GetMask("Ground")))
{
if (Input.GetButtonDown("Jump"))
{
gameObject.GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, 15f), ForceMode2D.Impulse);
}
}
}
So what this script does, it gets a reference to the player collider, and then checks if that player collider is touching the layer called ground. The ground needs to have a collider as well not just to prevent the player from fall through the level, but also to trigger this boolean to be true or false. True if they are touching each other, false if they are not. So, if they are not touching the ground then it doesnt matter how many times the player will press that button, he will not jump. Once they do then it applies the jump force if they are pressing Jump.
The Issue
I'm making a 2D Unity game where the main weapon of your character is a fireball gun. The idea is that a fireball will shoot out of the player's hand at the same angle the player's hand is pointing. I have 3 issues:
When I shoot the fireball, since the fireball is a RididBody, it pushes the player. This is because I've made the centre of the player's arm (the same place where the fireball shoots from) the point at which the arm rotates around the player (what is meant to be the shoulder);
To instantiate the fireball prefab on the arm, the only way I know how to do it is by using a piece of code which requires the arm to be a RigidBody. This means that the arm is affected by gravity and falls off the player on start unless I freeze the arm's y-axis movement, which means that when the player jumps, while the arm does not fall, it floats at the same y-position as where it started while moving along the x-axis; and
When the fireball is shot, the angle from which it is propelled after being shot is not the same angle as the angle of the player's arm.
Instantiating the Fireball
if (Input.GetKeyDown(KeyCode.Space))
{
pew.Play();
var fireballTransform = Instantiate(fireballPrefab); //creates a new shot sprite
fireballTransform.position = new Vector3(transform.position.x + horizMultiplier, transform.position.y, transform.position.z);
fireballTransform.rotation = orientation;
fireballTransform.transform.Rotate(0, 0, transform.rotation.z);
}
if (Input.GetKeyDown(KeyCode.D)) // moves right
{
orientation = 0;
horizMultiplier = 0.08F;
}
if (Input.GetKeyDown(KeyCode.A)) // moves left
{
orientation = 180;
horizMultiplier = -0.08F;
}
This piece of code is located within the script applied to the player's arm. The movement of the arm works fine and the problem seems to be either within this piece of code or the code for my fireball (which I will put next). A few definitions:
pew is a sound effect played when the fireball is shot;
horizMultiplier is the distance from the arm's centre which I would like the fireball to instantiate (also dependant of if the player) is facing left or right); and
orientation is which direction the player is facing (left or right). The fireball is then instantiated facing that same direction.
Fireball Script
public Vector2 speed = new Vector2(); // x and y forces respectively
private Rigidbody2D rb; // shorthand
private float rotation;
void Start()
{
rb = GetComponent<Rigidbody2D>(); // shorthand
rotation = rb.rotation;
if (rotation == 0)
{
rb.AddForce(Vector3.right * speed.x); // propels right
}
if (rotation == 180)
{
rb.AddForce(Vector3.left * speed.x); // propels left
}
}
I believe this code is explanatory enough with comments (if not please comment and I'll address any question). I believe an issue could also be in this piece of code because of the lines: rb.AddForce(Vector3.right * speed.x); and rb.AddForce(Vector3.left * speed.x); as these add directional forces to the object. I don't know is this is objective direction (right or left no matter what direction the object the force is being applied to is facing) or if it's right or left in terms of the object-- say if an object was rotated 90 degrees clockwise and that object had a force applied so that it moves right making the object move downwards.
What I'm expecting to happen is the player's arm will turn so that when a fireball is fired it is fired in the direction the arm is facing. The arms turning mechanics are fine, it's just trying to properly instantiate the fireball. Can anyone help with any of the issues I've laid out?
How I would go about this:
Add an empty transform as child of the arm and move it to where the fireball should spawn. Also make sure the rotation of it is such that its forward vector (the local z-axis) points to where the fireball should go. To see this, you need to set "Local" left of the play button.
Spawn the fireball at the transform's position with its rotation.
Ignore collision between the arm's/player's collider and the fireball's collider with https://docs.unity3d.com/ScriptReference/Physics.IgnoreCollision.html. If necessary, you can enable the collision between the two colliders again after 1s or so.
Set the fireball rigidbody's velocity to speed * rigidbody.forward.
If you need help with the code, please post it so I can see what's going on.
Edit:
The arm definitely doesn't require a Rigidbody.
You can just use Instantiate(prefab, position, rotation) as shorthand.
Is this for a 2D game?
Also, I'm going to sleep now but I'll gladly try to help tomorrow.
Your Issues
1) You should be masking the fireball's layer not to collide with your player's layer.
You can find more info about this here: https://docs.unity3d.com/Manual/LayerBasedCollision.html
(note: make sure you're on the Physics2D panel, and not the Physics one, as that's for 3D)
2) There is a setting called gravityScale in RigidBody2D. You should set this to 0 if you don't want gravity to be affecting your object. More info: https://docs.unity3d.com/Manual/class-Rigidbody2D.html
Fireball Instantiate
if (Input.GetKeyDown(KeyCode.Space))
{
pew.Play();
var position = hand.transform.position + hand.transform.forward * horizMultiplier;
var rotation = hand.transform.rotation;
var fireball = Instantiate<Fireball>(fireballPrefab, position, rotation);
fireball.StartMoving();
}
Fireball Script
private Rigidbody2D rb; // shorthand
private float rotation;
public float Speed;
public void StartMoving()
{
rb = GetComponent<Rigidbody2D>(); // shorthand
rb.velocity = transform.forward * Speed;
}
void OnTriggerEnter(....) { .... }
The problem i guess is in the waypoints script somewhere.
If i make the player to walk slow let's say speed 1 or 2 he walk to the first waypoint then turn to the next waypoint when he get to the second waypoint instead going back to the original position he make some round walking and then walk back to the first waypoint. What i want it to do is not to walk to the first waypoint but to walk to it's original start position.
Another thing if i change the player speed to 7 i see it walking fast when he get to the second waypoint he turn left and keep walking left none stop. Like depending on the speed of the player he act different.
This is a video clip showing the player on speed 2:
The second player who walk faster is on speed 2 see what happen when he get to the second waypoint:
video clip speed 2
And this is the second player on speed 7
video clip speed 7
And this is the waypoints script in c#
using UnityEngine;
using System.Collections;
public class Waypoints : MonoBehaviour {
public Transform[] waypoint;
public float patrolSpeed;
public bool loop = true;
public int dampingLook = 4;
public float pauseDuration;
private float curTime;
private int currentWaypoint = 0;
public CharacterController character;
// Use this for initialization
void Start () {
}
void LateUpdate(){
if(currentWaypoint < waypoint.Length){
patrol();
}else{
if(loop){
currentWaypoint=0;
}
}
}
void patrol(){
Vector3 nextWayPoint = waypoint[currentWaypoint].position;
// Keep waypoint at character's height
nextWayPoint.y = transform.position.y;
// Get the direction we need to move to
// reach the next waypoint
Vector3 moveDirection = nextWayPoint - transform.position;
if(moveDirection.magnitude < 1.5){
Debug.Log("enemy is close to nextwaypoint");
// This section of code is called only whenever the enemy
// is very close to the new waypoint
// so it is called once after 4-5 seconds.
if (curTime == 0)
// Pause over the Waypoint
curTime = Time.time;
if ((Time.time - curTime) >= pauseDuration){
Debug.Log("increasing waypoint");
currentWaypoint++;
curTime = 0;
}
}
else
{
Debug.Log("reaching in rotation " + moveDirection.magnitude);
// This code gets called every time update is called
// while the enemy if moving from point 1 to point 2.
// so it gets called 100's of times in a few seconds
// Now we need to do two things
// 1) Start rotating in the desired direction
// 2) Start moving in the desired direction
// 1) Let' calculate rotation need to look at waypoint
// by simply comparing the desired waypoint & current transform
var rotation = Quaternion.LookRotation(nextWayPoint - transform.position);
// A slerp function allow us to slowly start rotating
// towards our next waypoint
transform.rotation = Quaternion.Slerp(transform.rotation, rotation,
Time.deltaTime * dampingLook);
// 2) Now also let's start moving towards our waypoint
character.Move(moveDirection.normalized * patrolSpeed * Time.deltaTime);
}
}
}
Maybe this strange behaviour of the player is ok since the physics ? So if it's too fast it's making this behaviour ? But it's strange. I think no matter the speed it suppose to walk between the waypoints no ?
If i make the player to walk slow let's say speed 1 or 2 he walk to the first waypoint then turn to the next waypoint when he get to the second waypoint instead going back to the original position he make some round walking and then walk back to the first waypoint. What i want it to do is not to walk to the first waypoint but to walk to it's original start position.
This is because when the enemy reaches your last checkpoint you set the currentWaypoint to 0. So the next frame the enemy is going to look for that waypoint. and since your starting position isn't a waypoint, he will never walk to that spot again. What you could do is make another waypoint that is at the same place as your enemies startingposition. that way it will walk back to his original position.
Another thing if i change the player speed to 7 i see it walking fast when he get to the second waypoint he turn left and keep walking left none stop. Like depending on the speed of the player he act different.
This is because the movement (position) and rotation is not in sync with your patrolSpeed. you're only increasing the walk speed and not the rotationspeed. so every time your enemy has walked some distance, it needs to start rotating all over again. Try rotating your enemy with:
transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * patrolSpeed);
if thats still to slow try increasing the speedmodifier of the rotation.
As for the first problem, you never store the "original" position--you just have waypoints. So when the entity gets to the last waypoint, it sets course for the first waypoint, waypoint[0] since that's what LateUpdate() sets currentWaypoint to.
As for the entity changing behavior based on speed, two things to check-- is it actually reaching inside the if(moveDirection.magnitude < 1.5) block? If not, it's moving too fast in one frame to detect. But it looks like you're always moving in moveDirection which actually is not affected by the rotation you calculate, unless your Move() function works differently than I expect, and so it's likely that the entity keeps heading in one direction because of that.
I am working on a 3D side scroller game in which my camera is following my character using Vector3.lerp.
Currently my camera is looking at the player from a side but at certain points I want the camera to transition to TopView (look at the character from the top) while also keeping a certain height from the character.
I have done this by creating camera settings naming SideView and TopView. Now the problem is that the camera does transitions from SideView to TopView but during transition the camera shakes when it is at the end of the lerp (when the camera is almost at the target position).
How to make this smooth (stop camera shake)?
Note: both camera and character are moving.
Here the source code of my camera follow:
void LateUpdate ()
{
if(currentCameraSettings != null && target.transform != null)
{
targetPos = SetCameraTargetPos(target.transform);
if(duration != 0)
{
transform.position = Vector3.Lerp(transform.position,targetPos,(Time.time - startTime ) / duration );
}
else
{
transform.position = targetPos;
}
SetCameraRotation();
}
}
SetCameraTargetPos returns the target position after adding height and z-axis distance from the target character.
Sounds like you have the wrong situation for a Lerp. I think what you want to use here is MoveTowards.
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
if its still "jaggedy" for you, try using Time.smoothDeltaTime.
Sorry for bad english.
If you want prevent to camera shaking your use rigidbody properties.
Freeze x,y,z.
or
Create empty object inside the your object with all the same control settings and followed the empty space not the your object.