Unity3D Client cannot spawn prefab - c#

I am making shooting game over network and it works very well with below source.
player.cs
void Update(){
if (!isLocalPlayer) {
return;
}
if (Input.GetMouseButton(0)) {
CmdDefaultAttack(_skillDefault);
}
}
[Command]
protected void CmdDefaultAttack(GameObject _attackObject) {
GameObject bullet = (GameObject)Instantiate(_attackObject,_skill_Default_Spawn.position, _skill_Default_Spawn.rotation);
bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6.0f;
ClientScene.RegisterPrefab(bullet);
NetworkServer.Spawn(bullet);
Destroy(bullet, 2);
}
but what i want to do is to implement CmdDefaultAttack in another class inherited Interface and call it from Player.cs
source below
public interface ISkill {
void initiate(Player _player);
void CmdAttack();
bool CmdMotion();
}
public class SphereSkillDefault : NetworkBehaviour, ISkill {
Plyaer player;
public GameObject _skill_Default_Level1;
public GameObject _skill_Default_Cannon
public void initiate(Player_player) {
this.player = _player;
}
[Command]
public void CmdAttack() {
GameObject bullet = (GameObject)Instantiate(_skill_Default_Level1, player._skill_Default_Spawn.position, player._skill_Default_Spawn.rotation);
bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * 6.0f;
NetworkServer.Spawn(bullet);
Destroy(bullet, 2);
}
}
player.cs
ISkill _Idefaultattack;
public GameObject _defaultattack;
void Start() {
if (NetworkServer.active)
Debug.Log("Actived");
else
Debug.Log("DeActived");
if (isLocalPlayer) {
_Idefaultattack = _defaultattack.GetComponent<SphereSkillDefault>();
_Idefaultattack.initiate(this);
}
}
void Update() {
if (!isLocalPlayer) {
return;
}
_Idefaultattack.CmdAttack();
}
The problem is when i try to shoot a bullet, it throws the error message saying "network server is not active. cannot spawn objects without an active server."
And only server player can shoot bullets and it actually shows up to clients.
But clients can shoot only on their side and it doesn't show up to other clients or server.
i am stuck in this problem for days. Can anyone give me any advice?
thanks.

This problem is direct call to other class. so, you need bridge function.
I have written the source.
ISkill _Idefaultattack;
public GameObject _defaultattack;
void Start() {
if (NetworkServer.active)
Debug.Log("Actived");
else
Debug.Log("DeActived");
if (isLocalPlayer) {
_Idefaultattack = _defaultattack.GetComponent<SphereSkillDefault>();
_Idefaultattack.initiate(this);
}
}
void Update() {
if (!isLocalPlayer) {
return;
}
//_Idefaultattack.CmdAttack();
CmdAck();
}
[Command]
void CmdAck(){
_Idefaultattack.CmdAttack();
}

Related

Unity - One client can control all player objects using gamepad (Netcode and Input System)

TL;DR Clients can control only their player objects with keyboard, but all players with gamepad (using Netcode for Game Objects, Unity Input System and a PS4 Controller)
I am working on a multiplayer game and I am relatively new to multiplayer programming. I am using Netcode for GameObjects.
I am using Unity Input System for handling inputs and I created 2 action maps currently. One for movements (with keyboard and gamepad) and one for attacking (with keyboard, gamepad and mouse).
I am trying to move the players in a Server Authoritative way; thus, I am using Server RPCs for handling movements.
The issue I am having is that, when I play with a Gamepad (PS4 controller), one of the clients can control the others. However, it works perfectly with Keyboard actions.
The code I'm using for the player movement is below:
[RequireComponent(typeof(Rigidbody))]
public class PlayerMovement : NetworkBehaviour
{
[SerializeField] private Rigidbody _rb;
[SerializeField] private float movementSpeed = 10f;
[SerializeField] private float jumpSpeed = 8f;
Vector3 _movementVector;
private PlayerInputActions playerInputActions;
private PlayerInputActions PlayerInputActions
{
get
{
if(playerInputActions != null)
{
return playerInputActions;
}
return playerInputActions = new PlayerInputActions();
}
}
public override void OnNetworkSpawn()
{
if(!IsOwner) { return; }
PlayerInputActions.PlayerMovement.Movement.performed += ctx => SetMovement(ctx.ReadValue<Vector2>());
PlayerInputActions.PlayerMovement.Movement.canceled += ctx => SetMovement(Vector2.zero);
PlayerInputActions.PlayerMovement.Jump.performed += PerformJump;
}
public override void OnNetworkDespawn()
{
if (!IsOwner) { return; }
PlayerInputActions.PlayerMovement.Movement.performed -= ctx => SetMovement(ctx.ReadValue<Vector2>());
PlayerInputActions.PlayerMovement.Movement.canceled -= ctx => SetMovement(Vector2.zero);
PlayerInputActions.PlayerMovement.Jump.performed -= PerformJump;
}
private void OnEnable() => PlayerInputActions.Enable();
private void OnDisable() => PlayerInputActions.Disable();
private void SetMovement(Vector2 inputVector) => _movementVector = new Vector3(inputVector.x, 0.0f, 0.0f);
private void PerformJump(InputAction.CallbackContext obj)
{
if (!IsOwner) { return; }
Vector3 jumpVector = Vector3.up;
HandleJumpServerRpc(jumpVector);
}
// Update is called once per frame
void FixedUpdate()
{
if(!IsLocalPlayer) { return; }
HandleMovement();
}
private void HandleMovement()
{
if (!IsOwner) { return; }
HandleMovementServerRpc(_movementVector);
}
#region Server
[ServerRpc]
private void HandleMovementServerRpc(Vector3 movementVector)
{
if(Vector3.Distance(movementVector, Vector3.zero) > 0.000001f)
{
Debug.Log($"Owner ID: {OwnerClientId}");
_rb.MovePosition(transform.position + movementVector * Time.deltaTime * movementSpeed);
}
}
[ServerRpc]
private void HandleJumpServerRpc(Vector3 jumpVector)
{
if (_rb.velocity.y == 0f)
{
_rb.AddForce(jumpVector * jumpSpeed, ForceMode.Impulse);
}
}
private void OnCollisionEnter(Collision collision)
{
Debug.Log($"Collided with: {collision.gameObject.name}");
}
#endregion
}
And following is the Action Map I created for movement:
I thought the issue is with the Unity Input System event subscriptions, but I could not find a fix so far for it.
Any help is appreciated!
I'm not sure if this will work but I did
public override void OnNetworkSpawn(){
if (!IsOwner) Destroy(this);
}

How can I bring a Player to ignore collision with another GameObject in code?

Im currently trying to code a health system for my game and I want my Player GameObject to ignore collision with the Health Potion GameObject if the Player has max health. My problem is that I cannot simply turn off the collision between the Player Layer and Health Potion Layer because I only want to ignore collision if the Player has Max Health. I tried doing it myself but it didn't work. Here's my code:
public class ExampleCodeUA : MonoBehaviour{
public int PlayerMaxHealth = 100, PlayerCurrentHealth;
public HealthBar healthBar;
private void Start()
{
PlayerCurrentHealth = PlayerMaxHealth;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("HealthPotion"))
{
if (PlayerCurrentHealth == PlayerMaxHealth)
{
Physics2D.IgnoreLayerCollision(6, 7);
}
else if (PlayerCurrentHealth > 50)
{
GetHealth(PlayerMaxHealth - PlayerCurrentHealth);
}
else if (PlayerCurrentHealth <= 50)
{
GetHealth(50);
}
}
}
void GetHealth(int healing)
{
PlayerCurrentHealth += healing;
healthBar.SetHealth(PlayerCurrentHealth);
}
}
You don't want to modify your physics configuration at runtime. What you could do to avoid the collision... is to make it impossible, by having nothing to collide with. Both of your objects have a collider. What you could do is to disable all existing HealthPotion colliders by modifying their code. An implementation could be the following:
using System.Collections.Generics;
using UnityEngine;
public class HealthPotion : MonoBehaviour
{
private static List<HealthPotion> _existingPotions = new List<HealthPotion>();
public static void EnablePotionsCollider(bool value)
{
foreach (var potion in _existingPotions)
{
potion._collider.enabled = value;
}
}
private Collider _collider;
void Awake()
{
_collider = GetComponent<Collider>();
}
void OnEnable()
{
_existingPotions.Add(this);
}
void OnDisable()
{
_existingPotions.Remove(this);
}
}
Then you just have to call the method when you want to enable/disable by doing
HealthPotion.EnablePotionsCollider(value you want);

interact with gameobjects in Unity

I want to interact with gameobjects in Unity. These objects might be keys, doors, chests, ...
Let's say there is a key on a table. When the player comes closer it should get outlined by a shader and a text appears "pick up key".
I created an interface for interactable gameobjects.
public interface IInteractable
{
string InteractabilityInfo { get; }
void ShowInteractability();
void Interact();
}
By doing this I can add the interface component to my Key script
public class Key : MonoBehaviour, IInteractable
{
public string InteractabilityInfo { get { return "some text here"; } }
public void ShowInteractability()
{
// Outline Shader maybe?
}
public void Interact()
{
// Do something with the key
}
}
When it comes to a script that checks if something is interactable I created a script that creates a raycast which checks for interactables. (I attach this script to my FPS camera)
public class InteractabilityCheck : MonoBehaviour
{
private const int RANGE = 3;
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
{
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ShowInteractability();
if (Input.GetKeyDown(KeyCode.E))
{
interactable.Interact();
}
}
}
}
}
This script tries to get the interface component and calls the methods from it if it's not null.
This code works fine but what I don't like is that the Raycast fires one per frame. Is there another way achieving interactability?
Yes, raycasting is "expensive" and calling it every single frame is not cool at all and there are many ways to avoid that. In your case you can simple restructure the code like that:
private void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
{
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ShowInteractability();
interactable.Interact();
}
}
}
}
Also a good idea is to get the gameplay logic in a separed method just so so you can modify it easier later and avoid spaggeti xaxa. Like so:
public void interactWithYourObject()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
{
IInteractable interactable = hit.collider.GetComponent<IInteractable>();
if (interactable != null)
{
interactable.ShowInteractability();
interactable.Interact();
}
}
}
and then in your update just call it if your condition is true like that:
private void Update()
{
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, RANGE))
{
interactWithYourObject()
}
}
An alternative method is to place a spherical trigger on the interactables, you can then control the range through the radius of the spherical trigger.
This is similar to another question I just answered so I've just reworked the code.
using UnityEngine;
[RequireComponent(typeof(SphereCollider))]
internal abstract class CollisionTrigger : MonoBehaviour
{
private bool _isPlayerInsideTrigger = false;
private void Awake()
{
if(!GetComponent<SphereCollider>().isTrigger)
{
Debug.LogError("Please set the sphere collider to a trigger.");
enabled = false;
return;
}
}
private void Update()
{
if(_isPlayerInsideTrigger)
{
FakeOnTriggerStay();
}
}
private void OnTriggerEnter(Collider collider)
{
if(!collider.CompareTag("Player")) return;
_isPlayerInsideTrigger = true;
}
public abstract void FakeOnTriggerStay();
private void OnTriggerExit(Collider collider)
{
if(!collider.CompareTag("Player")) return;
_isPlayerInsideTrigger = false;
}
}
This is an example to demonstrate what your Key class would look like using the provided class above.
internal class Key : CollisionTrigger
{
public override void FakeOnTriggerStay()
{
// Show the user they can now interact with it.
// Outline Shader maybe?
if(Input.GetKeyDown(KeyCode.E))
{
// Do something with the key.
}
}
}

Scripting in unity 3d

I am facing a problem in Unity3D. I have the same health script attached to both the player and the enemy. I want to show the game-over message when the player dies but the issue is that the game over message appears for both the player and enemy when they die.
My code looks like is that:
public class CharacterStats : MonoBehaviour
{
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
health = Mathf.Clamp (health, 0, 100);
}
public void damage(float damage)
{
health -= damage;
if(health<=0)
{
Die();
Application.LoadLevel(gameover);
}
}
void Die()
{
characterController.enabled = false;
if (scriptstodisable.Length == 0)
return;
foreach (MonoBehaviour scripts in scriptstodisable)
scripts.enabled = false;
if (ragdollmanger != null)
ragdollmanger.Raggdoll();
}
}
As you are using 1 script for both player and enemy. You should have different classes for both and implement an interface or derive from a base class to implement health:
public class Character : MonoBehaviour
{
public float Health;
public virtual void Damage(float damageValue)
{
Health -= damageValue;
}
public virtual void Die()
{
}
}
public Enemy : Character
{
public override void Die()
{
// do enemy related stuff
}
}
public Player : Character
{
public override void Die()
{
// do player related stuff.
// like game over screen
}
}
Hope this helps :)
You could use a bool to check whether CharacterStats is attached to the player, for example by adding a tag called ”Player” to the player game object and checking if gameObject.tag == “Player”, or you could equivalently name the game object ”Player” and check gameObject.name if you so wish.
You could then run the function for the game over message only if the game object is a player (isPlayer is true).
public class CharacterStats : MonoBehaviour
{
bool isPlayer = false;
// Use this for initialization
void Start ()
{
if(gameObject.tag == “Player”)
{
isPlayer = true;
}
}
// Update is called once per frame
void Update ()
{
health = Mathf.Clamp (health, 0, 100);
}
public void damage(float damage)
{
health -= damage;
if(health<=0)
{
if(isPlayer)
{
// Do Player-only stuff
}
// Do Stuff for both players and enemies
}
}
}

Unity3D - enemy not taking damage

In Unity3D my enemy is not taking damage upon colliding with my projectile explosion.
Although this is not the case as it the health variable is unaffected upon colliding with my projectile explosion.
My Enemy and Barrel classes inherit from Entity which handles the taking of damage (subtracting the damage variable from the health variable). Although only the barrel class is working as intended.
The tags are 100% correct and I would prefer to continue using inheritance so please no suggestions to change the method in which my classes take damage.
the class that Enemy and Barrel inherit from
using UnityEngine;
using System.Collections;
public class Entity : MonoBehaviour {
public float health = 25;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
public virtual void takeDamage(float dmg){
health -= dmg;
if (health <= 0){
Destroy(this.gameObject);
}
}
}
Enemy class
using UnityEngine;
using System.Collections;
public class Enemy : Entity {
private NavMeshAgent agent;
public GameObject target;
// Use this for initialization
void Start () {
agent = GetComponent<NavMeshAgent> ();
}
// Update is called once per frame
void Update () {
agent.SetDestination (target.transform.position);
}
}
Barrel class
using UnityEngine;
using System.Collections;
public class Barrel : Entity {
private Transform myTransform;
//Effects
public GameObject barrelExplosion;
public GameObject explosionDamage;
public GameObject explosionSound;
// Use this for initialization
void Start () {
myTransform = this.transform;
}
// Update is called once per frame
void Update () {
}
public override void takeDamage(float dmg){
health -= dmg;
if (health <= 0){
Instantiate(barrelExplosion, myTransform.position, myTransform.rotation);
Instantiate(explosionSound, myTransform.position, myTransform.rotation);
Instantiate(explosionDamage, myTransform.position, myTransform.rotation);
Destroy(this.gameObject);
}
}
}
ExplosionAOE the class that sends the damage
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ExplosionAOE : MonoBehaviour {
public float damage = 100.0f;
public float lifeTime = 0.05f;
private float lifeTimeDuration;
public List<GameObject> damageTargets = new List<GameObject>();
public float radius = 15.0f;
GameManager gameManager;
void Start() {
gameManager = GameObject.FindGameObjectWithTag("GameManager").GetComponent<GameManager>();
//Destroy (this.gameObject, lifeTime);
lifeTimeDuration = Time.time + lifeTime;
transform.GetComponent<SphereCollider>().radius = radius;
}
void Update() {
//Explosion finishes, damage targets and remove AOE field
if (Time.time > lifeTimeDuration) {
foreach (GameObject target in damageTargets) {
if (target != null) {
//Calculate damage based on proximity to centre of explosion
float thisDamage = ((radius - Vector3.Distance(target.transform.position, transform.position)) / radius) * damage;
print(thisDamage);
target.GetComponent<Entity>().takeDamage(thisDamage);
//target.SendMessage("takeDamage", damage); //<< This is not good code. Let's fix this!
}
}
Destroy(this.gameObject);
}
}
void OnTriggerEnter(Collider otherObject) {
if (otherObject.gameObject.tag == "Enemy") {
damageTargets.Add(otherObject.gameObject);
}
if (otherObject.gameObject.tag == "Player") {
Vector3 jumpVector = (otherObject.transform.position - transform.position).normalized;
jumpVector *= 25;
otherObject.GetComponent<CharacterMotor>().SetVelocity(jumpVector);
}
}
}
Sorry this is a bit of a lengthy one and EVERYTHING is tagged correctly so that is not the issue, thanks.
Problem 1.
Use "Debug.Log" everywhere
void OnTriggerEnter(Collider otherObject) {
Debug.Log("in trig");
Debug.Log("otherObject.gameObject.tag is " + otherObject.gameObject.tag);
if (otherObject.gameObject.tag == "Enemy") {
Debug.Log("a");
damageTargets.Add(otherObject.gameObject);
}
if (otherObject.gameObject.tag == "Player") {
Debug.Log("b");
Vector3 jumpVector = (otherObject.transform.position -
transform.position).normalized;
jumpVector *= 25;
otherObject.GetComponent<CharacterMotor>().SetVelocity(jumpVector);
}
}
In particular, in Entity and Enemy.
Questions such as this one are instantly answered by tracking with Debug.Log.
Problem 2.
It's a PITA getting the relationships between triggers, rigidbody, etc.
It's very likely that's a problem here.
http://docs.unity3d.com/Manual/CollidersOverview.html
Go down to the annoying "trigger action matrix" and work from there.
Problem 3.
As a rule, never use the "tags" feature in Unity. (They only added tags to help "hello world" tutorials.)
In practice you use layers everywhere and always:
(Layers are particularly essential in shooting games: every single category needs a layer.)
Problem 4.
The code shown is definitely looking good. Here's some example code not unlike yours for tips.
Trivial example, note the breakaway code (the returns) inside the OnTrigger, you should do that).
Also,
use extentions
everywhere and always in Unity. Quick tutorial
it's the #1 tip if you actually want to work professionally.
public class Enemy:BaseFrite
{
public tk2dSpriteAnimator animMain;
public string usualAnimName;
[System.NonSerialized] public Enemies boss;
[Header("For this particular enemy class...")]
public float typeSpeedFactor;
public int typeStrength;
public int value;
// could be changed at any time during existence of an item!
[System.NonSerialized] public FourLimits offscreen; // must be set by our boss
[System.NonSerialized] public int hitCount; // that's ATOMIC through all integers
[System.NonSerialized] public int strength; // just as atomic!
[System.NonSerialized] public float beginsOnRight;
private bool inPlay; // ie, not still in runup
void Awake()
{
boss = Gp.enemies;
}
..........
protected virtual void Prepare() // write it for this type of sprite
{
ChangeClipTo(bn);
// so, for the most basic enemy, you just do that.
// for other enemy, that will be custom (example, swap damage sprites, etc)
}
void OnTriggerEnter2D(Collider2D c)
{
// we can ONLY touch either Biff or a projectile. to wit: layerBiff, layerPeeps
GameObject cgo = c.gameObject;
if ( gameObject.layer != Grid.layerEnemies ) // if we are not enemy layer....
{
Debug.Log("SOME BIZARRE PROBLEM!!!");
return;
}
if (cgo.layer == Grid.layerBiff) // we ran in to Biff
{
Gp.billy.BiffBashed();
// if I am an enemy, I DO NOT get hurt by biff smashing in to me.
return;
}
if (cgo.layer == Grid.layerPeeps) // we ran in to a Peep
{
Projectile p = c.GetComponent<Projectile>();
if (p == null)
{
Debug.Log("WOE!!! " +cgo.name);
return;
}
int damageNow = p.damage;
Hit(damageNow);
return;
}
Debug.Log("Weirded");
}
public void _stepHit()
{
if ( transform.position.x > beginsOnRight ) return;
++hitCount;
--strength;
ChangeAnimationsBasedOnHitCountIncrease();
// derived classes write that one.
if (strength==0) // enemy done for!
{
Gp.coins.CreateCoinBunch(value, transform.position);
FinalEffect();
if ( Gp.superTest.on )
{
Gp.superTest.EnemyGottedInSuperTest(gameObject);
boss.Done(this);
return;
}
Grid.pops.GotEnemy(Gp.run.RunDistance); // basically re meters/achvmts
EnemyDestroyedTypeSpecificStatsEtc(); // basically re achvments
Gp.run.runLevel.EnemyGotted(); // basically run/level stats
boss.Done(this); // basically removes it
}
}
protected virtual void EnemyDestroyedTypeSpecificStatsEtc()
{
// you would use this in derives, to mark/etc class specifics
// most typically to alert achievements system if the enemy type needs to.
}
private void _bashSound()
{
if (Gp.biff.ExplodishWeapon)
Grid.sfx.Play("Hit_Enemy_Explosive_A", "Hit_Enemy_Explosive_B");
else
Grid.sfx.Play("Hit_Enemy_Non_Explosive_A", "Hit_Enemy_Non_Explosive_B");
}
public void Hit(int n) // note that hitCount is atomic - hence strength, too
{
for (int i=1; i<=n; ++i) _stepHit();
if (strength > 0) // biff hit the enemy, but enemy is still going.
_bashSound();
}
protected virtual void ChangeAnimationsBasedOnHitCountIncrease()
{
// you may prefer to look at either "strength" or "hitCount"
}
protected virtual void FinalEffect()
{
// so, for most derived it is this standard explosion...
Gp.explosions.MakeExplosion("explosionC", transform.position);
}
public void Update()
{
if (!holdMovement) Movement();
if (offscreen.Outside(transform))
{
if (inPlay)
{
boss.Done(this);
return;
}
}
else
{
inPlay = true;
}
}
protected virtual void Movement()
{
transform.Translate( -Time.deltaTime * mpsNow * typeSpeedFactor, 0f, 0f, Space.Self );
}
......
/*
(frite - flying sprite)
The very base for enemies, projectiles etc.
*/
using UnityEngine;
using System.Collections;
public class BaseFrite:MonoBehaviour
{
[System.NonSerialized] public float mpsNow;
// must be set by the boss (of the derive) at creation of the derive instance!
private bool _paused;
public bool Paused
{
set {
if (_paused == value) return;
_paused = value;
holdMovement = _paused==true;
if (_paused) OnGamePause();
else OnGameUnpause();
}
get { return _paused; }
}
protected bool holdMovement;
protected virtual void OnGamePause()
{
}
protected virtual void OnGameUnpause()
{
}
protected string bn;
public void SetClipName(string clipBaseName)
{
bn = clipBaseName;
}
}
Is more easy if in ExplosionAOE/OnTriggerEnter function you call the takeDamage function:
scriptCall = otherObject.GetComponent(EnemyScript);
scriptCall.takeDamage(damage);

Categories