I'm working on an RTS game using mirror and ran into an issue.
I'm working on auto-attack function for the units.
Everything works for the host but not on clients.
Any help would be very much appreciated, I've been at it for a few days already.. Help!
Or at least point me in right direction here.
I will optimize this a bit better later on I just need to understand why it's not working on client. I've also noticed that Client will add itself to enemies list, it seems to be ignoring "hasAuthority" check
It's a bit of a write up so I'll try to make it as understandable as possible.
Unit gets instantiated and this is the script attached to it:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
using System.Linq;
public class UnitFiring : NetworkBehaviour
{
[SerializeField] private Targeter targeter = null;
[SerializeField] private GameObject projectilePrefab = null;
[SerializeField] private Transform projectileSpawnPoint = null;
[SerializeField] private float fireRange = 10f;
[SerializeField] private float fireRate = 1f;
[SerializeField] private float rotationSpeed = 20f;
private float lastFireTime;
//auto attack
[SerializeField] private Transform ownAimAtPoint = null;
[SerializeField] private LayerMask layerMask;
[SerializeField] private int updateFunctionFrequency = 60; //in frames
[ServerCallback]
private void Update()
{
if (Time.frameCount % this.updateFunctionFrequency != 0) return;
//runs update every 60 frames
enemyColliders.RemoveAll(Collider => Collider == null);
//if enemyCollider List has GameObject that was destroyed or null, it removes it from the list.
Targetable target = targeter.GetTarget();
if(target == null)
{
ArrayDetect();
AttackUnit();
return;
}
if (!CanFireAtTarget()) { return; }
//look at target
Quaternion targetRotation =
Quaternion.LookRotation(target.transform.position - transform.position);
//Rotate
transform.rotation = Quaternion.RotateTowards
(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
if(Time.time > (1 / fireRate) +lastFireTime)
{
Quaternion projectileRotation = Quaternion.LookRotation(
target.GetAimAtPoint().position - projectileSpawnPoint.position);
GameObject projectileInstance = Instantiate(
projectilePrefab, projectileSpawnPoint.position, projectileRotation);
NetworkServer.Spawn(projectileInstance, connectionToClient);
lastFireTime = Time.time;
}
}
[Client]
private bool CanFireAtTarget()
{
return (targeter.GetTarget().transform.position - transform.position).sqrMagnitude
<= fireRange * fireRange;
}
[SerializeField] private Collider[] colliderArray;
[SerializeField] private List<GameObject> enemyColliders;
Next is the ArrayDetect Function
Detect unit with Physics.OverlapSphere, make sure it has the authority and add it to enemyCollider List, I also added layerMask to make sure it's only checking for Units.
[Server]
private void ArrayDetect()
{
colliderArray = Physics.OverlapSphere(ownAimAtPoint.position, fireRange, layerMask);
foreach (Collider collider in colliderArray)
{
Debug.Log("we hit a", collider);
if (!collider.TryGetComponent<Targetable>(out Targetable potentialTarget))
{
return;
}
if (potentialTarget.hasAuthority)
{
return; //if the hit target is the players, do nothing
}
else
{
enemyColliders = enemyColliders.Distinct().ToList();
enemyColliders.Add(collider.gameObject);
Debug.Log("Found an enemy", potentialTarget);
}
}
}
Now AttackUnit Function, it will go thought enemyColliders List and set them as target to attack
[ServerCallback]
private void AttackUnit()
{
foreach (GameObject enemy in enemyColliders)
{
Debug.Log("We got confirmed enemy", enemy);
//GetComponent<Targeter>().CmdSetTarget(enemy);
targeter.CmdSetTarget(enemy);
//attack the enemy
}
}
This is Targetable Script, it simply returns AimAtPoint:
public class Targetable : NetworkBehaviour
{
[SerializeField] private Transform aimAtPoint = null;
public Transform GetAimAtPoint()
{
return aimAtPoint;
}
}
This is the CmdSetTarget Command in Targeter Script:
[Command]
public void CmdSetTarget(GameObject targetGameObject)
{
if(!targetGameObject.TryGetComponent<Targetable>(out Targetable newTarget)) { return; }
//if game object does not have a target, return
target = newTarget;
}
This is the error I get in the console, but only on the client Units, the host runs as it should:
Trying to send command for object without authority. Targeter.CmdSetTarget
Thanks for taking time to read this
Related
i am following a brackeys tutorial, the sprite is moving left right but not jumping. "Jump" is working fine but the results are not present in the gameview.it references a script provided by brackeys but i dont whether thats the problem.
Here is the code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class player_movemnt : MonoBehaviour
{
public CharacterController2D controller;
float horizontal_movement =0f;
public float run_speed = 60f;
bool jump = false;
void Update()
{
horizontal_movement = Input.GetAxisRaw("Horizontal")*run_speed;
if(Input.GetButtonDown("Jump"))
{
jump = true;
Debug.Log("l");
}
}
void FixedUpdate()
{
//move character
controller.Move(horizontal_movement*Time.fixedDeltaTime,false, jump);
jump= false;
}
}
here is the CharacterControl2D scrip
using UnityEngine;
using UnityEngine.Events;
public class CharacterController2D : MonoBehaviour
{
[SerializeField] private float m_JumpForce = 400f; // Amount of force added when the player jumps.
[Range(0, 1)] [SerializeField] private float m_CrouchSpeed = .36f; // Amount of maxSpeed applied to crouching movement. 1 = 100%
[Range(0, .3f)] [SerializeField] private float m_MovementSmoothing = .05f; // How much to smooth out the movement
[SerializeField] private bool m_AirControl = false; // Whether or not a player can steer while jumping;
[SerializeField] private LayerMask m_WhatIsGround; // A mask determining what is ground to the character
[SerializeField] private Transform m_GroundCheck; // A position marking where to check if the player is grounded.
[SerializeField] private Transform m_CeilingCheck; // A position marking where to check for ceilings
[SerializeField] private Collider2D m_CrouchDisableCollider; // A collider that will be disabled when crouching
const float k_GroundedRadius = .2f; // Radius of the overlap circle to determine if grounded
private bool m_Grounded; // Whether or not the player is grounded.
const float k_CeilingRadius = .2f; // Radius of the overlap circle to determine if the player can stand up
private Rigidbody2D m_Rigidbody2D;
private bool m_FacingRight = true; // For determining which way the player is currently facing.
private Vector3 m_Velocity = Vector3.zero;
[Header("Events")]
[Space]
public UnityEvent OnLandEvent;
[System.Serializable]
public class BoolEvent : UnityEvent<bool> { }
public BoolEvent OnCrouchEvent;
private bool m_wasCrouching = false;
private void Awake()
{
m_Rigidbody2D = GetComponent<Rigidbody2D>();
if (OnLandEvent == null)
OnLandEvent = new UnityEvent();
if (OnCrouchEvent == null)
OnCrouchEvent = new BoolEvent();
}
private void FixedUpdate()
{
bool wasGrounded = m_Grounded;
m_Grounded = false;
// The player is grounded if a circlecast to the groundcheck position hits anything designated as ground
// This can be done using layers instead but Sample Assets will not overwrite your project settings.
Collider2D[] colliders = Physics2D.OverlapCircleAll(m_GroundCheck.position, k_GroundedRadius, m_WhatIsGround);
for (int i = 0; i < colliders.Length; i++)
{
if (colliders[i].gameObject != gameObject)
{
m_Grounded = true;
if (!wasGrounded)
OnLandEvent.Invoke();
}
}
}
public void Move(float move, bool crouch, bool jump)
{
// If crouching, check to see if the character can stand up
if (!crouch)
{
// If the character has a ceiling preventing them from standing up, keep them crouching
if (Physics2D.OverlapCircle(m_CeilingCheck.position, k_CeilingRadius, m_WhatIsGround))
{
crouch = true;
}
}
//only control the player if grounded or airControl is turned on
if (m_Grounded || m_AirControl)
{
// If crouching
if (crouch)
{
if (!m_wasCrouching)
{
m_wasCrouching = true;
OnCrouchEvent.Invoke(true);
}
// Reduce the speed by the crouchSpeed multiplier
move *= m_CrouchSpeed;
// Disable one of the colliders when crouching
if (m_CrouchDisableCollider != null)
m_CrouchDisableCollider.enabled = false;
} else
{
// Enable the collider when not crouching
if (m_CrouchDisableCollider != null)
m_CrouchDisableCollider.enabled = true;
if (m_wasCrouching)
{
m_wasCrouching = false;
OnCrouchEvent.Invoke(false);
}
}
// Move the character by finding the target velocity
Vector3 targetVelocity = new Vector2(move * 10f, m_Rigidbody2D.velocity.y);
// And then smoothing it out and applying it to the character
m_Rigidbody2D.velocity = Vector3.SmoothDamp(m_Rigidbody2D.velocity, targetVelocity, ref m_Velocity, m_MovementSmoothing);
// If the input is moving the player right and the player is facing left...
if (move > 0 && !m_FacingRight)
{
// ... flip the player.
Flip();
}
// Otherwise if the input is moving the player left and the player is facing right...
else if (move < 0 && m_FacingRight)
{
// ... flip the player.
Flip();
}
}
// If the player should jump...
if (m_Grounded && jump)
{
// Add a vertical force to the player.
m_Grounded = false;
m_Rigidbody2D.AddForce(new Vector2(0f, m_JumpForce));
}
}
private void Flip()
{
// Switch the way the player is labelled as facing.
m_FacingRight = !m_FacingRight;
// Multiply the player's x local scale by -1.
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
i am completely dumbfounded with this problem.
If you're using a separate class to handle your movement, it's always ideal to include it alongside your question. So others wont have to search for it.
Your code appears okay, at least based on the tutorial you've mentioned. Double-check that them_GroundCheck GameObject of CharacterController2D is on the ground rather than on the bottom of the player, as that's responsible for ensuring you can jump in the first place.
public CharacterController2D characterController;
public float runSpeed = 60f;
private float _horizontalMovement;
private bool _jump;
private void Update()
{
_horizontalMovement = Input.GetAxisRaw("Horizontal") * runSpeed;
if (Input.GetButtonDown("Jump"))
{
_jump = true;
}
}
private void FixedUpdate()
{
characterController.Move(_horizontalMovement * Time.fixedDeltaTime, false, _jump);
_jump = false;
}
I'd recommend working backwards and ensuring your CharacterController2D is set up correctly to support the jump, including any attributes you need to add in the inspector window. Such as a reference to GroundCheck and its LayerMask.
Video showing the host and client side perspectives
I am using rigidbodies on the balls, which are not instantiated, and they are picked up via script¹, the host can access everything, but the client cannot access the rigidbodies, but supposedly can access the script (video explains the process). I am relatively new to Netcode, so if it's something obvious, talk to me as if I am a 2 year old, and, preferably, point me in the right direction, or unity doc.
¹
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GetBall : MonoBehaviour
{
[SerializeField] private Camera cam;
[SerializeField] private float distanceGrab;
[SerializeField] private Transform ballHold;
[SerializeField] private Transform ballThrow;
[SerializeField] private GameObject ballGet;
[SerializeField] private GameObject _ball;
[SerializeField] private bool isHoldingBall;
[SerializeField] private float charge;
[SerializeField] private Rigidbody rb;
[SerializeField] private RectTransform slider;
[SerializeField] private GameObject pickUpDot;
[SerializeField] private GameObject reticle;
void Update()
{
if(_ball != null)
{
_ball.transform.position = ballHold.position;
_ball.transform.rotation = new Quaternion(transform.rotation.x, transform.rotation.y, transform.rotation.z, transform.rotation.w);
}
if(isHoldingBall == false)
{
GrabBall();
}
if(isHoldingBall == true)
{
ThrowBall();
}
}
void OnTriggerEnter(Collider ball)
{
}
void GrabBall()
{
ballGet.SetActive(true);
reticle.SetActive(false);
if(ballGet.transform.GetComponent<BallInSphere>().canPickUp == true)
{
pickUpDot.SetActive(true);
if(Input.GetButtonDown("Fire1"))
{
_ball = ballGet.transform.GetComponent<BallInSphere>().theBall;
rb = _ball.GetComponent<Rigidbody>();
rb.useGravity = false;
rb.isKinematic = true;
}
}
else if(ballGet.transform.GetComponent<BallInSphere>().canPickUp == false)
{
pickUpDot.SetActive(false);
}
if(_ball != null)
{
isHoldingBall = true;
}
}
void ThrowBall()
{
ballGet.SetActive(false);
pickUpDot.SetActive(false);
reticle.SetActive(true);
slider.sizeDelta = new Vector2(slider.transform.localScale.x * charge * 100f, 100f);
if(_ball != null)
{
if(Input.GetButton("Fire2"))
{
charge += 20f * Time.deltaTime;
if(charge >=10)
{
charge = 10;
}
}
if(!Input.GetButton("Fire2"))
{
charge -= 20f * Time.deltaTime;
if(charge <= 0)
{
charge = 0;
}
}
if(Input.GetButtonDown("Fire1") && charge > 0)
{
_ball.transform.GetComponent<I_Am_Ball>().canHit = true;
_ball.transform.position = ballThrow.position;
rb = _ball.GetComponent<Rigidbody>();
rb.useGravity = true;
rb.isKinematic = false;
rb.AddForce(cam.transform.forward * charge * 300);
isHoldingBall = false;
_ball = null;
rb = null;
charge = 0f;
slider.sizeDelta = new Vector2(0f, 100f);
}
}
}
}
I've tried adding the Network Rigidbody with Network Transform (All of my objects (players and balls) have the Network Object component attached to them), which didn't work. I have tried using my PlayerNetwork script, which makes the actual players move, but my players don't use rigidbodies and the balls do, so it didn't really help. I have tried my hand at RPC's, but really dont understand it. If that's what I need to use, then I'll gladly read through the doc on it, but if I can avoid it, that would be great too.
Thanks in advance.
Edit: After trying my hand at Rpc's I've realized that an Rpc is not the answer to my question. Although I cam across something called Client Side Predictions, which I am currently looking into. If I'm on the right track, or the wrong track, I'd be thankful for someone to let me know.
Im throwing a bomb using physics made by code. For some reason it doesnt detect collision unless my physics stop applying force to the object. in order to bypass it I cancelled the applied force and applied gravity to it on collision enter and put the movement of the bomb to LateUpdate so it will trigger after the OnCollisionEnter but the bomb collides only most of the time with the floor (mesh collision, the floor made with ProBuilder) and not all of the time. the bomb collision detection is set to continuous
Will appreciate all the help, Thanks!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BombBehavior : MonoBehaviour
{
[SerializeField] float ExplosionForce = 300;
[SerializeField] float ExplosionRadius;
[SerializeField] float LaunchForceX;
[SerializeField] float LaunchForceY;
[SerializeField] float Delay;
float countdown;
Rigidbody rigidbodyy;
float gravity = 1;
Player player;
bool HasExploded = false;
// Start is called before the first frame update
void Start()
{
rigidbodyy = GetComponent<Rigidbody>();
player = Player.p;
GetLaunchForceX();
countdown = Delay;
}
private void GetLaunchForceX()
{
if (transform.position.x > player.transform.position.x)
{
LaunchForceX *= 1;
}
else if (transform.position.x < player.transform.position.x)
{
LaunchForceX *= -1;
}
}
private void LateUpdate()
{
ThrowBomb();
}
private void Update()
{
countdown -= Time.deltaTime;
if (countdown <= 0 && !HasExploded)
{
ExplodeNearEnemy();
}
}
private void ThrowBomb()
{
if (rigidbodyy.useGravity == false)
{
Vector3 ThrowDirection = new Vector3(LaunchForceX, LaunchForceY, 0);
LaunchForceY -= gravity;
ThrowDirection.y = LaunchForceY;
transform.Translate(ThrowDirection * Time.deltaTime);
}
}
private void ExplodeNearEnemy()
{
Collider[] colliders = Physics.OverlapSphere(transform.position, ExplosionRadius);
foreach (Collider collider in colliders)
{
if (collider.gameObject.CompareTag("Enemy"))
{
Rigidbody enemyRB = collider.GetComponent<Rigidbody>();
if (enemyRB != null)
{
enemyRB.useGravity = true;
enemyRB.AddExplosionForce(ExplosionForce, transform.position, ExplosionRadius);
Destroy(enemyRB.gameObject,1);
}
}
}
//Destroy(gameObject);
}
private void OnCollisionEnter(Collision collision)
{
LaunchForceY = 0;
LaunchForceX = 0;
gravity = 0;
rigidbodyy.useGravity = true;
}
}
I don't exactly understand the problem but it looks like you want to make a Bomb/Granate so why dont you just write a function and use AddForce()?
void Thow(Vector3 direction, float strength)
{
rigidbodyy.AddForce(direction * strength, ForceMode.Impulse);
}
something like this should help as you only need to get the throwing direction and apply a strength then Unity will handle the rest
Or maybe if your bomb doesn't collide with the ground give the ground a rigidbody and set kinematic to true
Use the MovePosition() to move a rigid body if you want colliders etc to work. Teleporting a rigid body by altering the transform directly messes up the physics.
I'm trying to solve an issue in Unity 2d game creation.
So, my issue is when the subject is idle, the 2d player object should be idle and when I move the character, it must animate like walking.
But in my case, the walking animation is not working but the character is moving.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField]
private float jumpmovement = 11f;
[SerializeField]
private float movement = 10f;
private float movementx;
[SerializeField]
private Rigidbody2D mybody;
private Animator anim;
[SerializeField]
private string Walk_Ani = "Player is walking";
private SpriteRenderer sr;
private void Awake()
{
mybody = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
sr = GetComponent<SpriteRenderer>();
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
Playerkeymove();
animateplayer1();
}
void Playerkeymove()
{
movementx = Input.GetAxisRaw("Horizontal");
// Debug.Log(movementx);
transform.position += new Vector3(movementx, 0f, 0f) * movement * Time.deltaTime;
// Debug.Log(transform.position);
}
void animateplayer1()
{
// anim.SetBool(Walk_Ani , true);
// we are going to the right side
if (movementx > 0)
{
anim.SetBool(Walk_Ani, true);
sr.flipX = false;
}
else if (movementx < 0)
{
// we are going to the left side
anim.SetBool(Walk_Ani, true);
sr.flipX = true;
}
else
{
anim.SetBool(Walk_Ani, false);
}
}
}
I can't understand, why I have a warning issue with boolean part where I guess that's where my animation takes part.
You are trying to set a boolean parameter in the Animator that doesn't exists. The code is correct. Check what name you choose for the bool parameter to active correctly the animation.
So i made a little side scroller that uses a prefab to spawn bullets.
My problem is that it only shoots to one side... the Right.
I need it to fire to the left as well. I've already made a variable to see if the player is looking to the right or left.
I've tried to put the Speed to -20 and i've tried to rotate it 180 degrees on it's Z axis. I tested if the bullet script even picked up the change from the player movement script and it does.
Player Movement script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
public GameObject bullet;
private Rigidbody2D myRigidbody;
private float speed = 15;
private bool facingRight;
private bool ground = false;
private float jump = 23;
// Start is called before the first frame update
void Start()
{
facingRight = true;
myRigidbody = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void FixedUpdate()
{
float horizontal = Input.GetAxis("Horizontal");
Movement(horizontal);
Flip(horizontal);
if (Input.GetKey("w"))
{
if (ground)
{
GetComponent<Rigidbody2D>().velocity = new Vector2(GetComponent<Rigidbody2D>().velocity.x, jump);
}
}
if(facingRight == false)
{
bullet.GetComponent<bullet>().left = true;
}
if (facingRight == true)
{
bullet.GetComponent<bullet>().left = false;
}
}
void OnTriggerEnter2D()
{
ground = true;
}
void OnTriggerExit2D()
{
ground = false;
}
private void Movement(float horizontal)
{
myRigidbody.velocity = new Vector2(horizontal * speed,myRigidbody.velocity.y);
}
private void Flip(float horizontal)
{
if (horizontal > 0 && !facingRight || horizontal < 0 && facingRight)
{
facingRight = !facingRight;
Vector3 theScale = transform.localScale;
theScale.x *= -1;
transform.localScale = theScale;
}
}
}
Weapon script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class weapon : MonoBehaviour
{
// Start is called before the first frame update
public bool right;
public Transform firepointR;
public GameObject bulletPrefab;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown("space"))
{
Debug.Log("Oh well");
Shoot();
}
}
void Shoot()
{
Instantiate(bulletPrefab, firepointR.position, firepointR.rotation);
}
}
bullet script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bullet : MonoBehaviour
{
public bool left;
public float speed = 20;
public Rigidbody2D rb;
// Start is called before the first frame update
void Start()
{
rb.velocity = transform.right * speed;
}
// Update is called once per frame
void FixedUpdate()
{
Debug.Log(speed);
if (left == false)
{
transform.Rotate(0, 0, 180);
}
}
}
As i previously said i need my bullet (prefab) to go the opposite direction but whatever i do right now it will always go right.
Expanding on my comment:
Did you try reversing the velocity?
rb.velocity = -(transform.right * speed);
Note: There are instances when using a negative float wont return the opposite of the positive result. However, in this example, it will work just fine.
I imagine this will work also (and is probably the correct way of doing it):
rb.velocity = transform.left * speed;