So I'm trying to build this basic multiplayer game, it's sort of a mmorpg game.
This is how it works so far..
When I connect to the game, I can see my character walking around just fine, but when my friend connects and stands still and I walk around, I can see my character walking and doing the walk animation, but so is his character, and his character also moves even tho he is standing still on his computer.
This is the setup.. When I walk (Press W) the client sends the input to the server, and then the server sends back a packet to the client which then updates the players position, this is also where I'm trying to update the players animation.
/// <summary>
/// Packet that's received when the server updates the players position.
/// This happens every game tick, even if the player is standing still
/// </summary>
/// <param name="_packet"></param>
public static void PlayerPosition(Packet _packet)
{
//The players id
int _id = _packet.ReadInt();
//The players position
Vector3 position = _packet.ReadVector2();
//GameManager.players[_id].transform.position = position;
//var clientAnimator = GameManager.players[_id].GetComponent<Animator>();
if (position != GameManager.players[_id].position)
{
GameManager.players[_id].GetComponent<Animator>().SetBool("isWalking", true);
GameManager.players[_id].position = position;
}
else
{
GameManager.players[_id].GetComponent<Animator>().SetBool("isWalking", false);
}
}
Is there anything that I'm doing wrong that is super obvious?
To the why:
I bet your player is a prefab so both player instances have an Animator component in which one and the same AnimatorController is referenced.
As a result setting a Bool or changing to any State in it affects both players' Animator. Since this is not synchronized it happens only on your side but for both players simultaneously.
I don't know if this is possible in an easy way on runtime but a fix for this would probably consist in creating a new individual AnimatorController instance (clone) for each player instance.
Maybe it would be possible to simply clone the Animator.RuntimeAnimatorController something like
[RequireComponent(typeof(Animator))]
public class AnimatorSetup : MonoBehaviour
{
// Reference this via the Inspector
[SerializeField] private Animator _animator;
private void Awake()
{
// As fallback get it on runtime
if(!_animator) _animator = GetComponent<Animator>();
// Clonet he currently reference animator controller
var newController = Instantiate(_animator.runtimeAnimatorController);
// Use the new instance of the controller for this animator
_animator.runtimeAnimatorController = newController;
}
}
and put this on the player prefab next to the Animator component.
Related
Sorry if this is a duplicate question.
I found this Reddit post which is exactly my current problem.
https://www.reddit.com/r/Unity2D/comments/lw96s5/multiplayer_cameras/
However, there's only one comment which isn't that descriptive and doesn't have any links to the 'tutorial'. To rephrase, I have a MLAPI game in Unity with a LocalPlayer prefab(has a camera, FPS movement code, and PlayerInput) and a ServerPlayer(does not have a camera or PlayerInput component) prefab. They represent different replicated players in my game.
The current problem is the code to decide whether to spawn a local player or a remote player.
Deciding what prefab to spawn:
private void ApprovalCheck(byte[] connectionData, ulong clientId, MLAPI.NetworkManager.ConnectionApprovedDelegate callback)
{
//Your logic here
bool approve = connectionData == System.Text.Encoding.ASCII.GetBytes("xd");
bool createPlayerObject = true;
if (NetworkManager.Singleton.ConnectedClients[clientId].PlayerObject.IsLocalPlayer)
{
localPlayerPrefab.GetComponent<NetworkObject>()
.SpawnAsPlayerObject(clientId);
}
else
{
serverPlayerPrefab.GetComponent<NetworkObject>()
.SpawnAsPlayerObject(clientId);
}
//If approve is true, the connection gets added. If it's false. The client gets
disconnected
callback(createPlayerObject, null, approve, positionToSpawnAt, Quaternion.identity);
}
Let me know if you need any extra details.
As said I think you should always spawn the same prefab for all players and rather have a component like e.g.
public class LocalPlayerControl : NetworkBehaviour
{
void Start()
{
if (IsLocalPlayer) return;
var cam = GetComponentInChildren<Camera>();
cam.enabled = false;
// or a bit more radical
Destroy(cam);
// or if your cam is on a sub object with additional components (e.g. usually AudioListener etc)
Destroy(cam.gameObject);
// same for all other components that you don't want a remote player to have
}
I am following the 2D Roguelike tutorial from unity learn.
When testing my game I enter the play mode and everything is fine, when using the arrows to move the player, unity stops and I can't click at any of the buttons in the editor, and the animation stops.
Here is my player script :
//Delay time in seconds to restart level.
public float restartLevelDelay = 1f;
//Number of points to add to player food points when picking up a food object.
public int pointsPerFood = 10;
//Number of points to add to player food points whne picking up a soda object.
public int pointsPerSoda = 20;
//How much damage a player does to a wall whne chopping it.
public int wallDamage = 1;
//Used to store a refrence to the Player's animator component
private Animator animator;
//Used to store player food points total during level.
private int food;
//Start overrides the Start function of MovingObject
protected override void Start()
{
//Get a component reference to the Player's animator component
animator = GetComponent<Animator>();
//Get the current food point total stored in GameManager.instance between levels.
food = GameplayManager.instance.playerFoodPoints;
//Call the Start function of the MovingObject base class.
base.Start();
}
//This function is called when the behaviour becomes disabled or inactive.
private void OnDisable()
{
//When Player object is disabled, store the current local food total in the GameManager so it can be re-loaded in next level.
GameplayManager.instance.playerFoodPoints = food;
}
private void Update()
{
//If it's not the player's turn, exit the function.
if (!GameplayManager.instance.playersTurn) return;
int horizontal = 0; //Used to store the horizontal move direction.
int vertical = 0; //Used to store the vertical move direction.
//Get input from the input manager, round it to an integer and store in horizontal to set x axis move direction
int v = (int) (Input.GetAxisRaw("Horizontal"));
horizontal = v;
//Get input from the input manager, round it to an integer and store in vertical to set y axis move direction
vertical = (int) Input.GetAxisRaw ("Vertical");
//Check if moving horizontally, if so set vertical to zero.
if (horizontal != 0)
{
vertical = 0;
}
//Check if we have a non-zero value for horizontal or vertical
if (horizontal != 0 || vertical != 0)
{
//Call AttemptMove passing in the generic parameter Wall, since that is what Player may interact with if they encounter one (by attacking it)
//Pass in horizontal and vertical as parameters to specify the direction to move Player in.
AttemptMove<Wall>(horizontal, vertical);
}
}
//AttemptMove overrides the AttemptMove function in the base class MovingObject
//AttemptMove takes a generic parameter T which for Player will be of the type Wall, it also takes integers for x and y direction to move in.
protected override void AttemptMove<T>(int xDir, int yDir)
{
//Every time player moves, subtract from food points total.
food--;
//Call the AttemptMove method of the base class, passing in the component T (in this case Wall) and x and y direction to move.
base.AttemptMove<T>(xDir, yDir);
//Hit allows us to reference the result of the Linecast done in Move.
RaycastHit2D hit;
//If Move returns true, meaning Player was able to move into an empty space.
if (Move(xDir, yDir, out hit))
{
//Call RandomizeSfx of SoundManager to play the move sound, passing in two audio clips to choose from.
}
//Since the player has moved and lost food points, check if the game has ended.
CheckIfGameOver();
//Set the playersTurn boolean of GameManager to false now that players turn is over.
GameplayManager.instance.playersTurn = false;
}
//OnCantMove overrides the abstract function OnCantMove in MovingObject.
//It takes a generic parameter T which in the case of Player is a Wall which the player can attack and destroy.
protected override void OnCantMove<T>(T component)
{
//Set hitWall to equal the component passed in as a parameter.
Wall hitWall = component as Wall;
//Call the DamageWall function of the Wall we are hitting.
hitWall.DamageWall(wallDamage);
//Set the attack trigger of the player's animation controller in order to play the player's attack animation.
animator.SetTrigger("playerChop");
}
//OnTriggerEnter2D is sent when another object enters a trigger collider attached to this object (2D physics only).
private void OnTriggerEnter2D(Collider2D other)
{
//Check if the tag of the trigger collided with is Exit.
if (other.tag == "Exit")
{
//Invoke the Restart function to start the next level with a delay of restartLevelDelay (default 1 second).
Invoke("Restart", restartLevelDelay);
//Disable the player object since level is over.
enabled = false;
}
//Check if the tag of the trigger collided with is Food.
else if (other.tag == "Food")
{
//Add pointsPerFood to the players current food total.
food += pointsPerFood;
//Disable the food object the player collided with.
other.gameObject.SetActive(false);
}
//Check if the tag of the trigger collided with is Soda.
else if (other.tag == "Soda")
{
//Add pointsPerSoda to players food points total
food += pointsPerSoda;
//Disable the soda object the player collided with.
other.gameObject.SetActive(false);
}
}
//Restart reloads the scene when called.
private void Restart()
{
//Load the last scene loaded, in this case Main, the only scene in the game.
SceneManager.LoadScene(0);
}
//LoseFood is called when an enemy attacks the player.
//It takes a parameter loss which specifies how many points to lose.
public void LoseFood(int loss)
{
//Set the trigger for the player animator to transition to the playerHit animation.
animator.SetTrigger("playerHit");
//Subtract lost food points from the players total.
food -= loss;
//Check to see if game has ended.
CheckIfGameOver();
}
//CheckIfGameOver checks if the player is out of food points and if so, ends the game.
private void CheckIfGameOver()
{
//Check if food point total is less than or equal to zero.
if (food <= 0)
{
//Call the GameOver function of GameManager.
GameplayManager.instance.GameOver();
}
}
How can I fix this?
I probably tried everything.
Regards.
This feels like an infinite loop to me, which would probably lock up the game and editor.
The code you posted calls out to other methods not listed here... I would look for a place where you might be recursively calling a method, or maybe where you're meaning to call base.theMethod but instead called this.theMethod.
This is probably a stack overflow where a function is calling himself infinitely.
It can also be a Coroutine from the base class with a infinite loop.
However, we can't say exactly where in the code, because is missing some information like what's inside the parent class (from the comments I suppose is called MovingObject)
Whenever posting a code be sure to send only the necessary piece of code (maybe removing some useless comments) and giving precious information like the inheritance and if is an override function would be useful to give also his base implementation.
I'm working on a multiplayer game and I'm encountering a issue when 2 players are loading the same level using PhotonNetwork.LoadLevel().
When I start the game alone, I can control my player and everything is fine. But when we are 2 players, Player A is controlling Player B and vice-versa.
I check a lot of links on the internet these past few days, and I learned the concept of PhotonNetwork.IsMine which I thought would solve all of my problem but it seems to not working with me. Also, I'm using the new input system of Unity but I don't think the issue come from here.
Basically, what I'm doing is:
Instantiate a player (this happened twice since I have 2 players) which have a PlayerManager
Player Manager Get Instance of the local player and synchronize camera with the local player only if isMine = true
CameraManager creates input manager if the script is linked to the local player by using isMine
Link the main camera to this script when the gamemanager request it
Update camera rotation only when isMine is true (second protection)
Here is a piece of my code:
GameManager.cs (Holding by a Scene Object, so it is initially instantiated for everyone with the scene)
void Start()
{
if(PlayerManager.LocalPlayerInstance == null)
{
//Get player's team
string team = (string)PhotonNetwork.LocalPlayer.CustomProperties["Team"];
int indexPlayer = GetSpawnPosition();
//Spawn player depending on its team and its index in the players pool
if (team.Equals("Spy"))
{
PhotonNetwork.Instantiate(this.spyPrefab.name, SpySpawns[indexPlayer].position, SpySpawns[indexPlayer].rotation);
}
else if (team.Equals("Defender"))
{
PhotonNetwork.Instantiate(this.defenderPrefab.name, SpySpawns[indexPlayer].position, SpySpawns[indexPlayer].rotation);
}
}
}
PlayerManager.cs (Holding by the player, so not initially instantiated with the scene)
void Awake()
{
//Keep track of the localPlayer to prevent instanciation when levels are synchronized
if (photonView.IsMine)
{
LocalPlayerInstance = gameObject;
}
//Don't destroy this gameobject so it can survives level synchronization
DontDestroyOnLoad(gameObject);
}
private void Start()
{
//Get Manager of the camera of the player and attach to the local player
CameraLookFPS cameraFPSManager = gameObject.GetComponent<CameraLookFPS>();
if (cameraFPSManager != null)
{
//Ensure that we the local player is controlling its own camera
if(photonView.IsMine == true)
{
cameraFPSManager.SynchronizeWithLocalPlayer();
}
}
else
Debug.Log("This player is missing the CameraLookFPS component");
}
CameraManager.cs (Holding by the player, so not initially instantiated with the scene)
private void Start()
{
//Synchronize camera with local player on start for debug
if(SynchronizeOnStart)
{
SynchronizeWithLocalPlayer();
}
//Bind input for controlling the camera
BindingInput();
}
private void BindingInput()
{
// Prevent control is connected to Photon and represent the localPlayer
if (photonView.IsMine == false && PhotonNetwork.IsConnected == true)
{
return;
}
else
{
//Get Components
Input_Master = new InputMaster();
//Enable
Input_Master.Enable();
//Input binding
Input_Master.Player.Look.performed += context => MoveCamera(context);
Input_Master.Player.Look.Enable();
}
}
public void SynchronizeWithLocalPlayer()
{
if (photonView.IsMine == false && PhotonNetwork.IsConnected == true)
{
return;
}
Player_Camera = Camera.main.transform;
isSynchronized = true;
}
I tried to be clear enough, tell me if something is bad explained. I'll continue my research on my side and I'll keep you in touch if I find something.
Thanks in advance for your help!
Adrien
I finally found my solution. It appears that I already read about it but I have failed when trying to solve it.
So, like a lot of people, it is a Camera issue.
What I did is:
Create a game object holding the camera and put it as a child of the player gameobject
Deactivate the game object holding the camera
When instantiating the player, check if the player is the local player, using PhotonNetwork.IsMine. If yes, activate the gameobject holding the camera through script
If you have questions, send me a message!
Adrien
I am having an issue where only the first NavMeshAgent can be created.
First of all, I selected all mesh renderers for the NavMesh bake objects:
I fine-tuned the bake a bit so it had just the right amount of detail. The black gaps around the various objects would be larger if I used a larger radius in the baked agent options:
I have two spawners placed on either side of the back ramp (the one with the white sphere in it). These are invisible but in a script, NavMeshObjects are placed onto these locations on an interval and have their destinations set to one of the other gameobjects.
The first one works fine, but none of the other 17 NavMeshObjects in the spawner queue can have their destinations set.
public class MinionSpawner : MonoBehaviour {
public void spawn (GameObject minion, GameObject target_position_obj) {
// Find the target position from the passed gameobject
MinionTargetPosition target_position = target_position_obj.GetComponent<MinionTargetPosition>();
if (!target_position) { throw new System.Exception("no valid target position given"); }
// Instantiate the given minion at the spawner's origin point
GameObject new_minion = Object.Instantiate(minion, transform.position, new Quaternion(0,0,0,0));
new_minion.SetActive(true);
// Mark the minion at it's target position, for lookup upon collision or elsewhere
MinionAttributes minion_attrs = new_minion.GetComponentInChildren<MinionAttributes>(true);
if (!minion_attrs) { throw new System.Exception("object passed as minion does not have MinionAttributes"); }
minion_attrs.target_position = target_position;
// Configure a NavMeshAgent to navigate the minion from origin to target position.
NavMeshAgent nav_mesh_agent = minion_attrs.gameObject.GetComponent<NavMeshAgent>();
if (!nav_mesh_agent) { throw new System.Exception("minion doesn't have a nav mesh agent"); };
nav_mesh_agent.Warp(transform.position);
// ================================================================
// THIS LINE FAILS:
nav_mesh_agent.destination = target_position.transform.position;
// ================================================================
// mark the minion's position as occupied in the turret's dictionary
Turret turret = target_position.turret;
turret.minion_slots[target_position] = true;
// set the minion's parent to the minion spawner, for organization's sake.
minion.transform.parent = transform;
}
}
I'm getting the error "SetDestination" can only be called on an active agent that has been placed on a NavMesh. and I know there are questions out there about this. But I've tried really everything I've found suggested, including baking the NavMesh, setting the NavMeshAgent's initial position with Warp, and making sure the points are on the mesh.
The issue was something unrelated to the NavMeshagent. It was this line:
minion.transform.parent = transform;
I needed to change it to:
new_minion.transform.parent = transform;
Basically minion is the object that gets cloned and new_minion is the clone. So if I set the parent of minion, I am messing it up for future clones, I guess.
Okay so I'm working on like a hack and slash kind of game in Unity and I have an enemy object who I'd like have attack the player and deal damage using colliders. I have added an event to the enemy's attack animation that calls the function OnTriggerEnter(Collider other) that should deal the damage but I get a error saying "Object reference not set to an instance of an object" whenever I try it out. Does anyone have any ideas on how I could make this work?
This code snippet can be very helpful for you to have an idea. I made this in an old project and works perfectly.
In this case is a right leg kick animation.
NOTE: code snippet not tested. “Health” script source is not included.
public class MeleeAttackController : MonoBehaviour
{
public SphereCollider kickAttackSphere; // in this case the sphere collider must be child of the right foot
public float meleeAttackDamage = 50;
public AudioClip kickAttackClip;
CapsuleCollider m_capsule;
void Awake()
{
kickAttackSphere.isTrigger = true;
m_capsule = GetComponent<CapsuleCollider>();
}
void Event_KickAttack_RightLeg() // invoked by the kick animation events (in case you have many kick animations)
{
if(CheckSphereAndApllyDamage(kickAttackSphere))
{
if (kickAttackClip)
{
AudioSource.PlayClipAtPoint(kickAttackClip, weaponAttackSphere.transform.position); // play the sound
}
}
}
/// <summary>
/// Returns true if the sphere collider overlap any collider with a health script. If overlap any collider also apply a the meleeAttackDamage.
/// </summary>
bool CheckSphereAndApllyDamage(SphereCollider sphere)
{
// check if we hit some object with a health script added
Collider[] colliders = Physics.OverlapSphere(sphere.transform.position, sphere.radius, Physics.AllLayers, QueryTriggerInteraction.Ignore);
foreach (Collider collider in colliders)
{
if (collider == m_capsule) continue; // ignore myself
Health health = collider.GetComponent<Health>();
if (health)
{
// if the collider we overlap has a health, then apply the damage
health.TakeDamage(meleeAttackDamage, transform);
return true;
}
}
return false;
}
}