Shoot from gun in top down shooter Unity3D - c#

Currently I made bullet shoot from players position. Problem is that it shoots from player's center and not from the gun. I changed position of the bullet to make it shoot out from the gun. But the problem is that, when I start to rotate the player, bullet copies the rotation but not the position of the gun. It just stays on the same place. Here's my code:
public Transform firePoint;
public GameObject bulletPrefab;
public float bulletForce = 20f;
public void Shoot()
{
Vector3 newPosition = new Vector3(firePoint.position.x + 1, firePoint.position.y - 0.1f, firePoint.position.z);
GameObject bullet = Instantiate(bulletPrefab, newPosition, firePoint.rotation);
Rigidbody2D rb = bullet.GetComponent<Rigidbody2D>();
rb.AddForce(firePoint.right * bulletForce, ForceMode2D.Impulse);
yield return new WaitForSeconds(0.5f);
}

Your issue is that you have hard-coded offset (x = 1, y = -0.1f) change. When you rotate your player 90 degrees, the offset for gun end becomes different.
There are two solutions for this:
Solution #1
Place your fire point Transform at the tip of the gun, make it child of the gun. That way the Transform will always follow the end of the gun.
Now, for instantiating, you can use firepoint.position without any modifications.
There are some drawbacks to this, mainly having strict objects hierarchy and it becomes harder to dynamically change guns as you will have to find and reassign fire point for each of them.
Solution #2
Have Vector3 firePointOffset;.
Once instantiating, calcualte the position of the fire point by doing
var firePointPosition = playerTransform.position + Vector3.Scale(playerTransform.forward, firePointOffset)`;
transform.forward returns Vector3 that points, well, forward for that specific transform. Vector3.Scale allows multiplying two vectors x * x, y * y, z * z;

Related

Unity camera follows player script

I'm pretty new to Unity. I tried to create a script that the camera would follow the actor (with a little difference). Is there a way to improve the code? It works just fine. But I wonder if I did it the best way. I want to do it about as I wrote, so if you have any tips. Thank you
Maybe change Update to FixedUpdate ?
public GameObject player;
// Start is called before the first frame update
void Start()
{
player = GameObject.Find("Cube"); // The player
}
// Update is called once per frame
void Update()
{
transform.position = new Vector3(player.transform.position.x, player.transform.position.y + 5, player.transform.position.z - 10);
}
Making the camera following the player is quite straight forward.
Add this script to your main camera.
Drag the reference of the player object to the script and then you are done.
You can change the values in the Vector 3 depending on how far you want the camera to be from the player.
using UnityEngine;
public class Follow_player : MonoBehaviour {
public Transform player;
// Update is called once per frame
void Update () {
transform.position = player.transform.position + new Vector3(0, 1, -5);
}
}
Follows player in the back constantly when the player rotates, no parenting needed, with smoothing.
Piece of Knowledges:
Apparently, Quaternion * Vector3 is going to rotate the point of the Vector3 around the origin of the Vector3 by the angle of the Quaternion
The Lerp method in Vector3 and Quaternion stand for linear interpolation, where the first parameter gets closer to the second parameter by the amount of third parameter each frame.
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public Transform target;
public float smoothSpeed = 0.125f;
public Vector3 locationOffset;
public Vector3 rotationOffset;
void FixedUpdate()
{
Vector3 desiredPosition = target.position + target.rotation * locationOffset;
Vector3 smoothedPosition = Vector3.Lerp(transform.position, desiredPosition, smoothSpeed);
transform.position = smoothedPosition;
Quaternion desiredrotation = target.rotation * Quaternion.Euler(rotationOffset);
Quaternion smoothedrotation = Quaternion.Lerp(transform.rotation, desiredrotation, smoothSpeed);
transform.rotation = smoothedrotation;
}
}
This will always follow the player from the same direction, and if the player rotates it will still stay the same. This may be good for top-down or side-scrolling view, but the camera setup seems to be more fitting for 3rd person, in which case you'd want to rotate the camera when the player turns.
The easiest way to do this is actually not with code alone, simply make the camera a child of the player object, that way its position relative to the player will always stay the same!
If you do want to do it through code, you can change the code to be like this:
void Update()
{
Vector3 back = -player.transform.forward;
back.y = 0.5f; // this determines how high. Increase for higher view angle.
transform.position = player.transform.position - back * distance;
transform.forward = player.transform.position - transform.position;
}
You get the direction of the back of the player (opposite of transform's forward). Then you increase the height a little so the angle will be a bit from above like in your example. Last you set the camera's position to be the player's position and add the back direction multiplied by the distance. That will place the camera behind the player.
You also need to rotate the camera so it points at the player, and that's the last line - setting the camera's forward direction to point at the player.
Here is just another option. I always find it easier to have the variables which are populated in the inspector so you can adjust it and fine tune it as needed.
public GameObject player;
[SerializeField]
private float xAxis, yAxis, zAxis;
private void Update()
{
transform.position = new Vector3(player.transform.position.x + xAxis, player.transform.position.y + yAxis, player.transform.position.z + zAxis);
}

Adapting player behaviour to AI in unity

I have designed an attack method for a player that is full functional, however I am new to AI and have no idea as to where I would begin when it comes to adapting this into a state for an FSM.
protected void UpdateAttackState()
{
// check for input
float rot = transform.localEulerAngles.y + rotationSpeed * Time.deltaTime * Input.GetAxis("Horizontal");
Vector3 fwd = transform.forward * moveSpeed * Time.deltaTime * Input.GetAxis("Vertical");
// Tank Chassis is rigidbody, use MoveRotation and MovePosition
GetComponent<Rigidbody>().MoveRotation(Quaternion.AngleAxis(rot, Vector3.up));
GetComponent<Rigidbody>().MovePosition(_rigidbody.position + fwd);
if (turret) {
Plane playerPlane = new Plane(Vector3.up, transform.position + new Vector3(0, 0, 0));
// Generate a ray from the cursor position
Ray RayCast = Camera.main.ScreenPointToRay(Input.mousePosition);
// Determine the point where the cursor ray intersects the plane.
float HitDist = 0;
// If the ray is parallel to the plane, Raycast will return false.
if (playerPlane.Raycast(RayCast, out HitDist))
{
// Get the point along the ray that hits the calculated distance.
Vector3 RayHitPoint = RayCast.GetPoint(HitDist);
Quaternion targetRotation = Quaternion.LookRotation(RayHitPoint - transform.position);
turret.transform.rotation = Quaternion.Slerp(turret.transform.rotation, targetRotation, Time.deltaTime* turretRotSpeed);
}
}
if(Input.GetButton("Fire1"))
{
if (elapsedTime >= shootRate)
{
//Reset the time
elapsedTime = 0.0f;
//Also Instantiate over the PhotonNetwork
if ((bulletSpawnPoint) & (bullet))
Instantiate(bullet, bulletSpawnPoint.transform.position, bulletSpawnPoint.transform.rotation);
}
}
// Update the time
elapsedTime += Time.deltaTime;
}
This is completely dependent on how you want your state machine to work. An example of this would be the AI trying to seek and destroy other players.
Seeking State: AI would move around the map with a detection zone (collider) that would change state on trigger enter. This would then save a position of the detection's generalized location which would be used to move and rotate the AI tank towards that position.
Firing State: AI enters a new detection zone (collider) within firing range, turret will move to aim at target. AI will also move around the map to keep firing range at a maximum while also keeping track of the last known position of the enemy if they happen to go out of range.
Detecting Fire: Player or other AI fires their weapon this could increase the detection radius resulting in a seeking state being called.
After those functions are figured out, place a switch or something similar within the Update() to allow everything within the AI to run alongside Unity's functions.

Instantiate a prefab pointing in the direction of another object

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(....) { .... }

Player rotation and camera rotation

i want the player to look into the direction, the camera is looking.
The camera follows the player (3rd person game style).
I've tried it with
transform.localRotation = new Quaternion(transform.localRotation.x,
cameraMain.transform.localRotation.y,
transform.localRotation.z,
transform.localRotation.w);
but it doesn't work.
Sometimes the player starts rotating the other direction.
the following code will make the object (specified in the parameter) face in the direction of where the main camera is looking:
public void lookInDirectionOfCamera(Transform object) {
RayCastHit hit;
if (Physics.raycast(cameraMain.transform.position, cameraMain.transform.forward, out hit)) {
object.forward = hit.point - object.position;
}else { //if the raycast didnt hit anything, make the object face 100 units in front of the camera
Vector3 point = Camera.main.transform.position + Camera.main.transform.forward * 100;
object.forward = point - object.position;
}
}
This will make the player face the point that is forward to the camera. If you just want them to have the same rotation in the y-axis don't use Quaternions!
Instead, you can just do it with Euler angles:
transform.eulerAngles = new Vector3(transform.eulerAngles.x,
cameraMain.transform.eulerAngles.y,
transform.eulerAngles.y);
The reason not to use transform.localRotation is because that is a Quaternion. The y component in a Quaternion is not the same as the y-axis in a Euler angle (what you are used to seeing), Quaternions are very confusing so you should almost never set individual values in them. If you want to edit them only use the built-in methods.
Get the direction the camera is looking with cameraMain.transform.forward, make a copy with a y value of zero, then use Quaternion.SetLookRotation to look in that direction with the global up direction.:
Vector3 cameraDirection = cameraMain.transform.forward;
Vector3 characterLookDirection = new Vector3(cameraDirection.x,
0f,
cameraDirection.z);
Quaternion newRotation = new Quaternion();
newRotation.SetLookRotation(characterLookDirection, Vector3.up);
transform.rotation = newRotation;

Set player's boundary within sphere

I want to restrict player movement in the sphere, the schematic diagram show as below. If player movement is out of range, then restrict player to sphere max radius range.
How can I write C# code to implement it, like this?
These are my current steps:
Create 3D sphere
Create C# code append to sphere object
My code so far:
public Transform player;
void update(){
Vector3 pos = player.position;
}
I don't know how you calculate your player`s position but before assigning the new position to the player you should check and see if the move is eligible by
checking the new position distance form the center of the sphere
//so calculate your player`s position
//before moving it then assign it to a variable named NewPosition
//then we check and see if we can make this move then we make it
//this way you don't have to make your player suddenly stop or move it
//back to the bounds manually
if( Vector3.Distance(sphereGameObject.transform.position, NewPosition)< radius)
{
//player is in bounds and clear to move
SetThePlayerNewPosition();
}
What #Milad suggested is right but also include the fact you won't be able to "slide" on the sphere border if your movement vector even slightly goes outside the sphere :
(sorry for the crappy graphic skills...)
What you can do if you want to be able to "slide" on the sphere interior surface is get the angle formed between the player position and the X vector and then apply this angle with the :
public Transform player;
public float sphereRadius;
void LateUpdate()
{
Vector3 pos = player.position;
float angle = Mathf.Atan2(pos.y, pos.x);
float distance = Mathf.Clamp(pos.magnitude, 0.0f, sphereRadius);
pos.x = Mathf.Cos(angle) * distance;
pos.y = Mathf.Sin(angle) * distance;
player.position = pos;
}
Just make sure using this won't counter effect your player movement script (that's why I put it in LateUpdate() in my example).

Categories