First Android Game - Part 8 - Collecting coins July 21, 2023
This is part 8 of making my first Android Game. If you missed part7, you can find it here To collect coins I am going to spawn them with the same logic that I spawned the enemies. In CoinPoolInstance.cs I am using the same spawn location gameobject I used for spawning the enemies. There aren't any coin types anymore and the coin prefabs are on the Instance. On CoinPoolInstance.cs, I have the master calling the spawning coins coroutines The coroutines chooses a random location to spawn the coins, just like the enemies On Coin.cs I am getting the PhotonView component. All the Coin prefab have a PhotonView component and a Photon Transform Classics View component with Synchronize Position The OnTriggerEnter2D in Coin.cs checks if view.IsMine, if it is call a RPC function named CoinCollected and Calls PhotonNetwork.Destroy The CoinCollected RPC function spawns the coin text The Coroutine EnabledObject() in Coin.cs checks how long the coin stays before getting destroyed In CoinTExt.cs, it is reduced to the same logic as Explosion.cs where it gets Destroyed after timeBeforeDestroy seconds. In Update(), the text will be translated upwards. I am not worried about the text location getting synced because it will be roughly in the same location as where the coin is After testing, some bugs were noticed: (1) sometimes when two coins are close to each other and are collected at the same time, only one coin amount is added, this occurs on develop branch as well so this bug existed before the changes. (2)On the client side, sometimes the number of coins does not get added even though it is collided with the player. I added Debug.Log() in Player.cs where there is a check if Player collides with the coin, and the Debug.Log does not always get printed. From the results below, you can see that even though the coins collides with the player, it doesn't always print out the Debug.Log, however, the coins always disappears and the cointext is always spawned. This means on the Master, the PunRPC function that spawns the text is being called, but the collision is not being detected on the client side My hunch with the issue of the number of coins not getting added was due to PhotonNetwork.Destroy() getting called on the coins on master side before the client player collides with the coin. I thought of this because this issue appears to occur only if the coin collides with the client player head on in the front. This never occurs if the coin collides with the player on the side. Also, the RPC function which gets called on master only that spawns the coin text is always triggered but the coin does not always get added. So I did a test on EnemyBase.cs, if the enemy collides with the player, destroy the enemy. I also disabled the bullet so I can have the player collide with the enemy. In Player.cs, if the player collides with the enemy, print out enemy hit to the console. My hunch was correct as shown in the result. Now the question is how would I resolve this? This issue is occuring for both enemy and coin. Enemy was not noticeable because the bullet destroyed the enemy before hitting the player in the front I thought instead of doing PhotonNetwork.Destroy() in coin.cs, I can do it on Player.cs when player collides with the coin However, that did not work and I just got errors if the client player tries to destroy the coins using PhotonNetwork.Destroy(collision.gameobject) I tried a different approach similar to how I spawn the bullets and assign a Photon.Realtime.Player to the owner. In this case, I assign the the Photon.Realtime.Player to the Coin's Owner in Coin.cs when the coin collides with the player. Player.cs would a property so I can access Photon.Realtime.Player. That did not work because on the master, when the client player collides with the coin, the Photon.Realtime.Player is null. I thought adding a null check would resolve everything. However, all it resolved was the null error. The issue with the coins not getting added issue still persists. It appears that when the client collides with the coins, PunRPC function gets called, the destroyed, but the AddCoins might not have been sent over in time. However, even this logic would conflict, because it isn't always happening and the coins only doesn't get added when the coin hits the front of the player but not the side. Coin.cs I added debug logs to see when when owner is null or not null gets called. Coin.cs I added debug logs to see destroyed(master) and disabled(client) gets called Coin.cs I added debug logs to see when PunRPC gets called PlayerInformation.cs I added debug logs to see when AddCoins is called. I noticed that the client sometimes only has PunRPC getting called. This tells me that the master could call PhotonNetwork.Destroy() first before the client can do anything, including OnTriggerEnter2D I discarded the changes. In Coin.cs, when the coin collides with the player, I would get PhotoView component and call a public RPC function from the Player.cs In Player.cs, the RPC function would add the coins to the players From the result you can see the coins getting added even when it is the client! However, a new problem occured, the coins does not always get added on the client when colliding from the side or the collection would be delayed. This confused me very much so I recorded both the client and the master. Below is the master recording. You can see that the client player did not collide with the treasure chest and the coin the near end so it makes sense the coin is not collected However, when I looked at the recording for the client, it looks like the client player collided with the treasure chest and the coin at the end. This now makes sense why the coin was not collected, because in Coin.cs it checks for PhotonView.IsMine before adding the coin. So the problem was not the code, I think it may be the positions of the coins not being synced up I took a screen shot of where the treasure chest was on the master I took a screen shot of where the treasure chest was on the client as well. Based on the testing, it seems like it's is because the player movement not being synced up at the exact time cause this mishap. This may also be the reason why when two coins are touched, both don't get added. However, I have encountered this issue after the changes I did consider changing the player's interpolation option on the PhotonTransformClassicView component to disabled or Syncronized Values. However, this just made the opposite player see very choppy movements I found a documentation on lag compensation. So in Player.cs, I added the interface IPunObservable. I then implemented public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info). My first attempt was to get send and receive the transform.position. I removed PhotonTransformClassicView component from the Player other wise it will conflict with the code. This did not work since all I'm doing is receiving a position which makes the receiving client look like it's teleporting. I then tried using the rigidbody2d position and velocity and included the lag compensation time but the result was still choppy I then thought combining the previous two methods where I only send the position, but then have the position multiply lag. However there was still noticeable lag, and the receiving client's player y position was lower then where it should be I then tried another approach where the sender is doing same thing in OnPhotonSerializeView. The receiver stores only the velocity information to the Rigidbody component of the object before calculating the time which has passed between sending and receiving the current message. The other information - position - are stored in a variable called networkPosition (Vector2). The receiver then multiplies the rigidbody's velocity by the passed time and adds up the result of this calcuation to the networkPosition variable In FixedUpdate() is where the target position is applied. Unfortunately, this caused the receiver player to move slowly - although the bullet is moving closer to the real speed I then removed the FixedUpdate() and in Update() tried to move the transform.position. However, this gave the same result as if I was using PhotonTransformViewClassic component where the receiver was just a bit slower. This got me thinking. If the player is lagging with lag compensation code or PhotonTransformViewClassic component, then wouldn't everything be lagging? I compared the coin movement of the master and client, and I can see that the coin is also lagging a little bit just like the player. So if everything is lagging at the same rate, this means I have to revisit my logic. Maybe having only the master to check if the coin colliding with the player is causing some confusion. I first did a test to see what happens if I try to PhotonNetwork.Destroy() in Coin.cs no matter if it's the client or master. I see that if it is the client that hits the coin then an error would occur This means that for master, I can destroy the gameobject, but on the client I should disable the gameobject. Then for all other coin related code such as spawning coin text and adding coins should be in a PunRPC function in Player.cs because I can check PhotonView.IsMine on master and client whereas in Coin.cs, the PhotonView.IsMine is always master due to PhotonNetwork.Instantiate The coins appear to be collecting now! However, the coin text appears to be spawning twice and the coin amount gets added twice. This means the RPC function is getting called twice because both the master and client are colliding with the coin. However, I thought the view.isMine on Player.cs would resolve this issue. It seems like view.IsMine is checked on all players when being used in a PunRPC, so the view.IsMine would be called on everyone thinking it is true. So in Coin.cs I checked if PhotonNetwork.LocalPlayer is equal to the player that collided with the coin then call the PunRPC Unfortunately, this brought me back to one of the earlier issues where the coins gets destroyed on master from PhotonNetwork.Destroy() before the coin text/coin amount gets added on client's end. This only happens if the coin hits the player on the front straight on and never on the side. The reason is because, of the lag. On Master, the coin would hit the player first, if this was the master player getting hit, the RPC function on Player.cs would get called and then the PhotonNetwork.Destroy() gets called. However, if this was the client collecting the coin, on Master, it would check if the local player is the master player - it is not - so it would call PhotonNetwork.Destroy(). Now on the client, because of the lag, the coin never gets the chance to call the RPC function from Player.cs, and the coin gets destroyed - the client may not have even gotten the chance to collide with the coin. In addition, the coin text is not spawning on the receiving end - so do this mean view.IsMine is actually checking properly on Player.cs?I moved the text spawning out of view.IsMine. The text started spawning again on all players. The issue with coin getting destroyed too early still persists I decided to do 3 checks in Coin.cs OnTriggerEnter2D. (1) if on master but is client that collides with coin. SetActive(false) the coin. (2) if it's master only, PhotonNetwork.Destroy() the coin (3) if it's client only,SetActive(false) the coin This appears to cause a problem when the coin collides with the client head on. The coin on the client will freeze. The coin on the master will be disabled. This is most likely due to the master coin not running anymore so on the client coin doesn't know what to do That made me think instead of disabling the gameobject for the case when the client collides with the coin on the master, I could disable the sprite renderer so that the gameobject is still active This appears to resolve most of the issues. The only issue left is collecting 2+ coins See the next blog part 9 here
Recent blogs See all blogs