Setting : Creating my first multiplayer game and running into an odd issue. it's a tank game where players can shoot bullets and kill each other
Problem : When the client shoots while moving, the bullet seems to spawn with a little delay which causes the the player to collide with the bullet.
The issue seems to be that the player itself is local and the bullet
is being spawned on the network ( which is causing the delay)
Note: The host player does not have this problem, therefore it's somehow related to networking.
How can I sync the bullet with the client player?
private void Fire(){
// Set the fired flag so only Fire is only called once.
m_Fired = true;
CmdCreateBullets ();
// Change the clip to the firing clip and play it.
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play ();
// Reset the launch force. This is a precaution in case of missing button events.
m_CurrentLaunchForce = m_MinLaunchForce;
}
[Command]
private void CmdCreateBullets()
{
GameObject shellInstance = (GameObject)
Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) ;
// Set the shell's velocity to the launch force in the fire position's forward direction.
shellInstance.GetComponent<Rigidbody>().velocity = m_CurrentLaunchForce * m_FireTransform.forward;
NetworkServer.Spawn (shellInstance);
}
Do your game rules need to cater for a player's tank being able to shoot itself?
If not, a simple solution is to have the projectile's collider ignore it's owner's collider when it's being activated:
Transform bullet = Instantiate(bulletPrefab) as Transform;
Physics.IgnoreCollision(bullet.GetComponent<Collider>(), GetComponent<Collider>());
I solved it the following way. if someone can look over to confirm my answer, it would be nice.
private void Fire(){
// Set the fired flag so only Fire is only called once.
m_Fired = true;
CmdCreateBullets ();
// Change the clip to the firing clip and play it.
m_ShootingAudio.clip = m_FireClip;
m_ShootingAudio.Play ();
// Reset the launch force. This is a precaution in case of missing button events.
m_CurrentLaunchForce = m_MinLaunchForce;
}
[Command]
private void CmdCreateBullets()
{
RpclocalBullet ();
}
[ClientRpc]
private void RpclocalBullet(){
GameObject shellInstance = (GameObject)
Instantiate (m_Shell, m_FireTransform.position, m_FireTransform.rotation) ;
// Set the shell's velocity to the launch force in the fire position's forward direction.
shellInstance.GetComponent<Rigidbody>().velocity = 25f * m_FireTransform.forward;
}
Related
I'm currently working on a unity project where I'm testing out how to create a projectile trajectory calculation method, which'll be applied to my overall larger Unity project. Inside of this sub-project is simply a ground plane, a square target GameObject, and my AI Enemy GameObject. For reference, here's what the hierarchy of my enemy GameObject looks like:
Enemy (capsule)
GunPivot (empty, is an anchor to rotate the Gun GameObject)
Gun (square)
BulletSpawn (empty, is the spawn point for any instantiated bullet)
Now how my bullet works inside of this scene is that my bullet has a rigidbody, and moves through the scene using the Unity's physics engine, and my gun GameObject has the script that spawns, aligns, and shoots the bullet forward. It just uses AddForce() and a FirePower variable, which can be set in the unity editor. Below is what the code looks like:
public class EnemyGunController : MonoBehaviour
{
//
// Properties & Fields
//
public GameObject BulletPrefab;
public GameObject BulletSpawn;
public float FireRate;
public float FirePower;
public bool CanFire;
//
// Method(s)
//
void Update()
{
StartCoroutine(Fire());
}
// Fires a bullet.
IEnumerator Fire()
{
if (CanFire)
{
CanFire = false;
// Instantiate a bullet. Set the rotation to be looking forwards relative to BulletSpawn
GameObject bullet = Instantiate(BulletPrefab, BulletSpawn.transform.position,
Quaternion.LookRotation(BulletSpawn.transform.up));
// Set bullet so that it ignores collisions with the gun.
Physics.IgnoreCollision(bullet.GetComponent<Collider>(), GetComponent<Collider>());
// Apply force to the bullet.
bullet.GetComponent<Rigidbody>().AddForce(BulletSpawn.transform.forward * FirePower, ForceMode.Impulse);
// Wait for the fire rate before firing again.
yield return new WaitForSeconds(FireRate);
CanFire = true;
}
}
}
Now here's where the issue now lies. I want my Enemy GameObject to hold a method that'll calculate at what rotational value GunPivot needs to be at in order for the Bullet to hit the target GameObject. I already know that Quaternion.LookAt() will rotate the gun to where the target is, but that only solves half of the issue. That rotates the gun horizontally to where the gun is (y-axis I believe), but now I need to check at which rotational value GunPivot needs to be at so that the bullet, which is a rigidbody and has non-zero values for its mass and drag, will hit the target GameObject.
I did look into this a bit and found out the Physics.Simulate() method and how I can calculate this in a separate scene, however when I ran the unity editor, it just froze and then crashed. I attached it to a separate GameManager GameObject, and here's the code for it below:
public class BulletPredictor : MonoBehaviour
{
//
// Properties & Fields
//
public GameObject BulletPrefab;
Scene _mainScene;
Scene _bulletPredictorScene;
PhysicsScene _mainPhysicsScene;
PhysicsScene _bulletPredictorPhysicsScene;
//
// Method(s)
//
void Start()
{
// Disable auto simulation.
Physics.autoSimulation = false;
// Set the main scene and main physics scene fields.
_mainScene = SceneManager.GetSceneByName("MainScene");
_mainPhysicsScene = _mainScene.GetPhysicsScene();
// Set the bullet predictor scene and bullet predictor physics scene fields.
CreateSceneParameters bulletPredictorSceneParameters = new(LocalPhysicsMode.Physics3D);
_bulletPredictorScene = SceneManager.CreateScene("BulletPredictorScene", bulletPredictorSceneParameters);
_bulletPredictorPhysicsScene = _bulletPredictorScene.GetPhysicsScene();
}
// FixedUpdate is called once per frame (fixed at 50 fps).
void FixedUpdate()
{
// Main scene physics need to work, so simulate main scene normally. Needs to be within a fixed update because variable fps can effect the physics simulation.
if (_mainPhysicsScene.IsValid())
{
_mainPhysicsScene.Simulate(Time.fixedDeltaTime);
}
}
// Simulates various shot trajectories to find an optimal shooting angle to hit target, then rotates enemy's gun in main scene to that angle.
public Quaternion ShootBullet(GameObject shooter, GameObject target, Vector3 shooterPos, Vector3 targetPos)
{
// Checks if the main or bullet predictor physics scene is valid. If it isn't just return a default rotation.
if (!_mainPhysicsScene.IsValid() || !_bulletPredictorPhysicsScene.IsValid())
return Quaternion.identity;
// Clone shooter, and move it to the bullet predictor scene.
GameObject shooterClone = Instantiate(shooter, shooterPos, Quaternion.identity);
SceneManager.MoveGameObjectToScene(shooterClone, _bulletPredictorScene);
// Also clone target, and move it to the bullet predictor scene.
GameObject targetClone = Instantiate(target, targetPos, Quaternion.identity);
SceneManager.MoveGameObjectToScene(targetClone, _bulletPredictorScene);
// Get a reference to shooterClone's GunPivot gameobject.
GameObject gunPivot = shooterClone.transform.Find("GunPivot").gameObject;
// Rotate gunPivot to face targetClone.
gunPivot.transform.LookAt(targetClone.transform);
while (gunPivot.transform.rotation.x > -90)
{
// Instantiate a bullet, and move it to the bullet predictor scene. Set the rotation to be the shooterClone's BulletSpawn's rotation.
GameObject bullet = Instantiate(BulletPrefab, gunPivot.transform.position, Quaternion.LookRotation(gunPivot.transform.up));
// Add force to the bullet.
bullet.GetComponent<Rigidbody>().AddForce(gunPivot.transform.forward * bullet.GetComponent<BulletBehavior>().InitialSpeed, ForceMode.Impulse);
// Loop for 200 iterations, and simulate the bullet.
for (int i = 0; i < 200; i++)
{
// Simulate bullet predictor scene.
_bulletPredictorPhysicsScene.Simulate(Time.fixedDeltaTime);
}
// if the bullet is destroyed, break.
if (bullet == null)
{
break;
}
Destroy(bullet);
gunPivot.transform.Rotate(-0.1f, 0, 0);
}
return gunPivot.transform.rotation;
}
}
For debugging purposes, I just added a variable that referenced the GunPivot on my Gun GameObject, and called the ShootBullet() before I actually started the Fire() method, but my unity editor ended up crashing every single time I ran with this code. I'm not entirely sure why this didn't work, although I believe that this happened since I was using Simulate() in a numerical calculation, which probably is VERY VERY computationally expensive.
I'd really appreciate any insight or tips on how to either improve the simulate code I have above or perhaps try out a different approach from it what I'm doing. Also FYI, you can assume that the target isn't moving, so that might make things easier I suppose?
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.
I'm a new unity's user, and I'm trying to make a little 2D plateformer.
I can controle my player but I have a little problem with my jump.
I made it with a trigger in animator, it works, but I want to stop the animation when player touching the ground.
The best will be to keep the last jump's frame prep until the player touch the ground, and after stop it.
I have a collider2D on a gameObjet who's attached to the player with this code:
void Start()
{
Audio = GetComponent<AudioSource>();
Anim = transform.parent.GetComponent<Animator>();
}
void OnTriggerEnter2D (Collider2D col)
{
if (col.gameObject.tag == "Sol" || col.gameObject.tag == "Plateforme")
{
Anim.SetTrigger("stopJump");
transform.parent.GetComponent<playerController>().isGrounded = true;
Audio.pitch = 0.7f;
Audio.volume = 0.7f;
Audio.PlayOneShot(soundGround);
}
}
My animator have a transition 'stopJump' between 'jump' 'idle' and run. Is it the right thing to do?
see my animator here
The animation Jump doesn't want to stop before the ending frame. If i disable 'has exit time', the jump stop too early...
The trigger 'stopJump' does not have priority...
Thanks!
In your state machine of the animator,
just add an idle state and a trigger which will transfer from JUMP state to that IDLE(which will loop when the player is standing on the ground and do nothing) state, then from the OnTriggerEnter2D you could use Animator.setTrigger("triggerName") to transfer the state to idle.
As in our project player should become idle in lots of condition, so we just made an AnyStat transfer to the idle state by trigging backIdel here is the example:
I have been trying to add little things on top of the tutorials Unity supplies and I am confused about how to get this certain mechanic to work.
When my player shoots it shoots in the direction of the mouse. When I run a client and host on my computer so that 2 people are connect for testing I get weird results.
I have both of my players shooting in the correct direction but when I am moving my mouse on one of the clients I see that my blue circle (which represents where the bullets will be spawned) is moving when that client is not focused, meaning I am not currently clicked on that client and when I am moving my mouse I see the blue circle moving on the client I am not focused on which I am not sure if my friend down the street was to test this would it cause errors.
Here are 2 screenshots of my Scene/Game view to get a better visual : Pic 1 - Pic 2
I ended up using Network Transform Child on my parent GameObject for one of my children GameObjects that helps spawn the location of the bullets but still the visual look from my Scene tab makes me worry about the accuracy of bullets being spawned.
Here is my shooting code that :
public class PlayerShooting : NetworkBehaviour
{
public GameObject bulletPrefab;
public GameObject fireSpot;
public float bulletSpeed;
public Transform rotater;
public Camera cam;
void Update()
{
// Only run the below code if this is the local player.
if (!isLocalPlayer)
{
return;
}
// Rotate based on the location of the mouse our spot to shoot bullets.
Vector3 dir = cam.ScreenToWorldPoint (Input.mousePosition) - rotater.position;
float angle = Mathf.Atan2 (dir.y, dir.x) * Mathf.Rad2Deg;
rotater.rotation = Quaternion.AngleAxis (angle, Vector3.forward);
// When we hit spacebar
if(Input.GetKeyDown(KeyCode.Space))
{
// Fire some bullets.
CmdFire ();
}
}
// Something the client is wanting to be done and sends this "command" to the server to be processed
[Command]
public void CmdFire ()
{
// Create the Bullet from the Bullet Prefab
var bullet = (GameObject)Instantiate (bulletPrefab, fireSpot.transform.position, Quaternion.identity);
// Add velocity to the bullet
bullet.GetComponent<Rigidbody2D>().velocity = (fireSpot.transform.position - rotater.position).normalized * bulletSpeed;
// Spawn the bullets for the Clients.
NetworkServer.Spawn (bullet);
// Destroy the bullet after 2 seconds
Destroy(bullet, 4.0f);
}
}
My movement script :
public class Movement : NetworkBehaviour {
void Update()
{
if (!isLocalPlayer)
{
return;
}
var x = Input.GetAxis("Horizontal") * Time.deltaTime * 3.0f;
var y = Input.GetAxis("Vertical") * Time.deltaTime * 3.0f;
transform.Translate(x, y, 0f);
}
}
This will continue to be a problem if you'd like to run multiple instances of unity games on the same machine. If you go into Edit > Project Settings > Player, there is a Run in Background option under Resolution and Presentation. If you have this checked, both instances of the game will receive updated mouse positions per frame from Input.mousePosition (Keep in mind Input.GetKeyDown will only register to the focused instance of the game so there's some inconsistency here). If you don't have that check box selected, your game instance will pause when not focused (I'm guessing you don't want this if you're working with a client/host networking paradigm).
There are a few ways you can work around this. The ideal way to test a networked game would be to have a unique PC for each instance of the game. If that's not an option, you can add some checks to ensure the mouse is over the window. As long as the two windows don't overlap you can do that by checking the position against the screen info:
bool IsMouseOverWindow()
{
return !(Input.mousePosition.x < 0 ||
Input.mousePosition.y < 0 ||
Input.mousePosition.x > Screen.width ||
Input.mousePosition.y > Screen.height);
}
You can then use this to decide, in your Update(), whether or not you want to update rotator.rotation.
Another option would be to implement MonoBehaviour.OnApplicationFocus. You can then enable/disable things (like updating rotations based on mousePosition) in response to that event. The cleanest solution would be to have a clear way for your systems to ask "is my window focused". You can make a class like this:
public class FocusListener : MonoBehaviour
{
public static bool isFocused = true;
void OnApplicationFocus (bool hasFocus) {
isFocused = hasFocus;
}
}
Make sure you have one of these somewhere in your game and then anything can check by looking at FocusListener.isFocused.
Working on 2D mode, I have a script attached to a Sprite. The Update() part is as follow:
void Update () {
if (Input.GetKeyDown ("left")) {
cursor.rigidbody2D.MovePosition (cursor.rigidbody2D.position - speedX);
} else if (Input.GetKeyDown ("right")) {
cursor.rigidbody2D.MovePosition (cursor.rigidbody2D.position + speedX);
} else if (Input.GetKeyDown ("up")) {
cursor.rigidbody2D.MovePosition (cursor.rigidbody2D.position + speedY);
} else if (Input.GetKeyDown ("down")) {
cursor.rigidbody2D.MovePosition (cursor.rigidbody2D.position - speedY);
}
}
where cursor is a Prefab linked in Inspector. The cursor prefab has only 2 components, Sprite Renderer (to define the image), and the Rigidbody 2D, which setting is as follow:
Mass = 1
Linear Drag = 0
Angular Drag = 0
Gravity Scale = 0
Fixed Angle = true
Is Kinematic = false
Interpolate = None
Sleeping Mode = Start Awake
Collision Detection = Discrete
But when I press the arrow keys, the sprite showing on screen does not move. What did I miss?
Tried to add Debug.Log() inside the if case, it really enters the case. And no error occurred.
Note: speedX = new Vector2(1,0); and speedY = new Vector2(0,1);
You're trying to move an object that doesn't exist from the game's perspective. Assigning cursor a prefab by dragging it from the assets library, is like assigning a blueprint. Telling the prefab to move is like yelling at a car's blueprint to accelerate :)
There are two general solutions.
1) Save a reference
Instantiate the prefab and save the resulting reference to manipulate your new object. You can directly cast it to Rigidbody2D if you're mainly interested in using the rigidbody. The cast will only work if your prefab variable is of the same type, otherwise it will raise an exception. This way will ensure that your prefab always contains a Rigidbody2D or you can't even assign it in the editor.
public Rigidbody2D cursorPrefab; // your prefab assigned by the Unity Editor
Rigidbody2D cursorClone = (Rigidbody2D) Instantiate(cursorPrefab);
cursorClone.MovePosition (cursorClone.position - speedX);
2) Assign the script to the prefab
Depending on your game and what you want to achieve, you can also add a script directly to the prefab. This way every instance of it would just control itself.
void Update () {
if (Input.GetKeyDown ("left")) {
rigidbody2D.MovePosition (rigidbody2D.position - speedX);
} else { //...}
}