I have an interface which all my monsters should implement.
namespace Assets.Scripts
{
interface IMonster
{
void setSpeed(float s);
float getSpeed();
void SetMonsterPosition(Vector2 pos);
Vector2 GetMonsterPosition();
void DestroyMonster();
void MoveMonster();
}
}
Then I have a concrete monster class (I will add more):
public class Monster2 : MonoBehaviour, IMonster
{
public Monster2()
{
speed = Random.Range(0.05f, 0.15f);
monster = (GameObject)Instantiate(Resources.Load("Monster2"));
float height = Random.Range(0, Screen.height);
Vector2 MonsterStartingPosition = new Vector2(Screen.width, height);
MonsterStartingPosition = Camera.main.ScreenToWorldPoint(MonsterStartingPosition);
monster.transform.position = MonsterStartingPosition;
}
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
And a factory class that will produce my monsters:
class MonsterFactory : MonoBehaviour
{
public static IMonster getMonster()
{
return new Monster2();
}
}
This works but I read that I should not use new and I should use AddComponent. So I tried something like:
class MonsterFactory : MonoBehaviour
{
public static GameObject mymonster; //#first
public static IMonster getMonster()
{
return mymonster.AddComponent<Monster2>(); //#second
}
}
The problem is that now when I am trying to run the game there is a an error NullReferenceException: Object reference not set to an instance of an object
IMonster monster = MonsterFactory.getMonster();
As David said mymonster needs to be initialised. But even then you will run into trouble, as there is one GameObject containing a bunch of Monster2 components.
So instead I suggest:
GameObject go = new GameObject (GameObjectName);
return go.AddComponent<Monster2>();
Now every new monster has its own GameObject and thus can move indepently.
Related
For example, I have a variable "Wisps" that I want to change when the player picks up an object. But I don't know how to do it. I tried to add a WispDisplay object to call the classes, like in Java, but it doesn't seem to work.
public class WispCode : MonoBehaviour
{
WispDisplay wd = new WispDisplay();
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
wd.setWisp(wd.getWisp()+1);
Destroy(gameObject);
}
}
}
public class WispDisplay : MonoBehaviour
{
public int Wisp = 5;
public Text WispText;
void Start()
{
}
void Update()
{
WispText.text = "Wisp: " + Wisp.ToString();
}
public int getWisp()
{
return Wisp;
}
public void setWisp(int newWisp)
{
Wisp = newWisp;
}
}
Easiest (a tiny bit dirty) way is to use a static variable. Downside: you can only have exactly ONE.
Example:
public class MyClass: MonoBehaviour {
public static int wisps;
}
Then, in ANY class, just use this to access it:
MyClass.wisps = 1234;
The more elegant way, working with multiple class instances, is using references.
Example:
public class PlayerClass: MonoBehaviour {
public int wisps = 0;
}
public class MyClass: MonoBehaviour {
public PlayerClass player;
void Update(){
player.wisps += 1;
}
}
Then, you need to drag-drop (aka "assign") the "PlayerClass" Component (attached to the player) to the the Gameobject that should increase the Wisps count. You can duplicate these objects after assigning the reference.
Now, if you actually want to have some sort of collectible, I'd suggest this approach:
You Have a Player "PlayerClass" and some Objects that are collectible, which have Trigger Colliders.
The objects have this code:
public class Example : MonoBehaviour
{
private void OnTriggerEnter(Collider other)
{
// probably a good idea to check for player tag:
// other.compareTag("Player");
// but you need to create the "Player" Tag and assign it to Player Collider Object.
if(TryGetComponent(out PlayerClass player))
{
player.wisps += 1;
}
}
}
I am working on a 2D platformer and I am using cinemachine to follow my player.
When a player drops off a platform under -20 y, the player is destroyed and instantiated as a clone to the spawn point as expected, but the camera is not following, as the original player is destroyed: it says missing on the "Follow" Slot.
Is there any way to solve it? I prefer using Destroy and Instantiate as respawning instead of teleporting the original player to the respawn point.
This is the respawn script (GameMaster)
public class GameMaster : MonoBehaviour
{
public static GameMaster gm;
void Start()
{
if (gm == null)
{
gm = GameObject.FindGameObjectWithTag("GM").GetComponent<GameMaster>();
}
}
public Transform playerPrefab, spawnPoint;
public int spawnDelay = 2;
public void RespawnPlayer() {
//yield return new WaitForSeconds(spawnDelay);
Instantiate(playerPrefab, spawnPoint.position, spawnPoint.rotation);
Debug.Log("ADD SPAWN PARITCAL");
}
public static void Killplayer(Player player) {
Destroy(player.gameObject);
gm.RespawnPlayer();
}
}
here is the player script if it is needed
public class Player : MonoBehaviour
{
[System.Serializable]
public class PlayerStats
{
public int Health = 100;
}
public PlayerStats playerStats = new PlayerStats();
public int FallBoundary = -20;
void FixedUpdate()
{
if (transform.position.y <= FallBoundary)
{
DamagePlayer(1000);
}
}
public void DamagePlayer(int damage) {
playerStats.Health -= damage;
if (playerStats.Health<=0)
{
Debug.Log("Kill Player");
GameMaster.Killplayer(this);
}
}
}
You can cache the cinemachine inside your start method and then assign to follow the player at respawn.
Your code will become
using Cinemachine;
public class GameMaster : MonoBehaviour
{
public static GameMaster gm;
public CinemachineVirtualCamera myCinemachine;
void Start()
{
if (gm == null)
{
gm = GameObject.FindGameObjectWithTag("GM").GetComponent<GameMaster>();
}
myCinemachine = GetComponent<CinemachineVirtualCamera>();
}
public Transform playerPrefab, spawnPoint;
public int spawnDelay = 2;
public void RespawnPlayer() {
//yield return new WaitForSeconds(spawnDelay);
var newPlayer = Instantiate(playerPrefab, spawnPoint.position, spawnPoint.rotation);
Debug.Log("ADD SPAWN PARITCAL");
myCinemachine.m_Follow = newPlayer;
}
public static void Killplayer(Player player) {
Destroy(player.gameObject);
gm.RespawnPlayer();
}
}
You must assign the new object to follow like this:
myCinemachine.m_Follow = spawnedPlayer;
I just recently got into Unity 3D and currently working on one of my first own projects. For the game im making, I need a spawner function, that respawns a clone of the enemy as soon as the enemy falls off the platform. This is the code I have right now:
using UnityEngine;
public class spawner : MonoBehaviour
{
public GameObject enemyPrefab;
public float spawnHeight = 0.75f;
// Start is called before the first frame update
void Start()
{
spawnEnemy();
}
// Update is called once per frame
void Update()
{
if (enemyClone.transform.position.y < -10)
{
Destroy(enemyClone);
spawnEnemy();
}
}
public void spawnEnemy()
{
var enemyPosition = new Vector3(Random.Range(-5, 5), spawnHeight, Random.Range(-5, 5));
var enemyClone = Instantiate(enemyPrefab, enemyPosition, Quaternion.identity);
}
}
The function spawnEnemy itself works fine, since it creates an enemy on game start, tho further enemies aren't spawned. I get the message: "Assets\spawner.cs(21,21): error CS0103: The name 'enemyClone' does not exist in the current context".
I do see why I get the message, don't know how to make enemyClone globally available however.
Thanks to everybody in advance,
bezunyl
In the spawnEnemy() function, you say var enemyClone = Instantiate(...);. enemyClone is a local variable that can only be used within the spawnEnemy function, or at least that's how you've written it.
If you want to use the enemyClone outside of the spawnEnemy function, you need to declare the enemyClone variable outside of the function. (The example below will work if you DON'T want enemyClone to be accessible to other GameObjects)
using UnityEngine;
public class spawner : MonoBehaviour
{
public GameObject enemyPrefab;
public float spawnHeight = 0.75f;
private GameObject enemyClone //Added to allow enemyClone to be used anywhere in the class
// Start is called before the first frame update
void Start()
{
spawnEnemy();
}
// Update is called once per frame
void Update()
{
if (enemyClone.transform.position.y < -10)
{
Destroy(enemyClone);
spawnEnemy();
}
}
public void spawnEnemy()
{
var enemyPosition = new Vector3(Random.Range(-5, 5), spawnHeight, Random.Range(-5, 5));
enemyClone = Instantiate(enemyPrefab, enemyPosition, Quaternion.identity);
}
}
Now if you want enemyClone to be accessible by other GameObjects, then you'll want to make the enemyClone variable public instead of private. If you don't want it to show up in the inspector, add [HideInInspector] above the declaration of enemyClone, as shown below:
[HideInInspector]
public GameObject enemyClone;
Your issue is based on scope. You might want to research it, it's important to know.
The scope of a variable determines its visibility to the rest of a program.
http://www.blackwasp.co.uk/CSharpVariableScopes.aspx
http://www.informit.com/articles/article.aspx?p=1609145&seqNum=4
Spawning GameObject on demand is expensive. Instead of spawning it everytime, you should pool the GameObject.
public class Spawner : MonoBehaviour {
public Enemy enemyPrefab;
public List<Enemy> enemyPool;
public const SPAWN_HEIGHT = 0.75f;
// Start is called before the first frame update
void Start()
{
enemyPool = new List<Enemy>();
spawnEnemy();
}
// Update is called once per frame
public void Despawn(Enemy deadEnemy)
{
deadEnemy.gameObject.SetActive(false);
enemyPool.Add(deadEnemy);
}
public void spawnEnemy() {
Enemy newEnemy;
if (enemyPool.Count > 0) {
newEnemy = enemyPool[0];
enemyPool.Remove(0);
} else {
newEnemy = Instantiate(enemyPrefab);
}
newEnemy.Init(this);
newEnemy.position = new Vector3(Random.Range(-5, 5), SPAWN_HEIGHT, Random.Range(-5, 5));
newEnemy.gameObject.SetActive(true);
}
}
public class Enemy : MonoBehaviour {
private Spawner spawner;
private const float DEATH_POSITION_Y = -10;
public void Init(Spawner spawner) {
this.spawner = spawner;
}
void Update() {
if (transform.position.y < DEATH_POSITION_Y) {
spawner.Despawn(this);
}
}
}
example I have class name "PlayerClass" and "StatClass"
PlayerClass
public float health = 100 ;
public float experience;
StatClass
PlayerClass playerclass = new PlayerClass()
public float health2;
public float experience2;
health2 = playerclass.health;
experience2 = playerclas.experience;
usually I have to create "PlayerClass playerclass = new PlayerClass()"
the result will be the same health2 and experience2;
but whenever i made a change on healt from player class with code of course, the result healt2 from StatClass will be the same it was 100
Sory for my bad english.
----------------- Edited -----
GameControl
public class GameControl : MonoBehaviour {
public static GameControl control;
public float health = 100;
public float experience = 1000;
// Use this for initialization
void Awake () {
/*
* Awake Start Before Start() Happen
*/
if (control == null) {
DontDestroyOnLoad (gameObject);
control = this;
} else if (control != this) {
Destroy (gameObject);
}
}
}
GetHealth
public class GetHealth : MonoBehaviour {
public static GetHealth getHealth;
GameControl gameControl = new GameControl();
//GameControl gameControl;
public static float health;
Text text;
void Awake () {
/*
* Awake Start Before Start() Happen
*/
if (getHealth == null) {
DontDestroyOnLoad (gameObject);
getHealth = this;
} else if (getHealth != this) {
Destroy (gameObject);
}
text = GetComponent<Text> ();
health = 0;
}
void Update(){
health = gameControl.health;
text.text = "Health: " + health;
}
}
I made Change on value variable Health in GameControl. but Get Health always access 100 the same as first time when variable GameControl haven't changed. I think it was maybe because we use "new" on GetHealth class.
GameControl gameControl = new GameControl();
Is there another way to solve this without "new".
If instantiation of player was in updateStat method and it is calling in void update() method, it cause to instantiate player object again and again. You can try this or instantiate playerclass2 in constructor. Also making health and experience static will help if only player object exist.
Class StatClass{
PlayerClass playerclass2;
public float health2;
public float experience2;
public void updateStat(){
if(playerclass2==null) playerclass2 = new PlayerClass();
health2 = playerclass2.health;
experience2 = playerclas2.experience;
}
}
EDIT
You can't use new keyword since it is derived from MonoBehavior. Have to use ClassName.method() format to call methods of derived classes from MonoBehavior
Thanks all, I have Found it.
It just have to
health = GameControl.control.health;
I don't have to instantiate it I just Call class and then initialize of class and then variable of class.
http://answers.unity3d.com/questions/32413/using-constructors-in-unity-c.html
That does not exactly solve the following problem:
Various Weapon Levels
Server-Client Architecture
Server wants to spawn a weapon or projectile, but BEFORE wants to set which player shot, which level the weapon has etc.
WeaponScript should handle all Effects, like Instantiation themselves
I could do it like so
GameObject fireball = (GameObject)GameObject.Instantiate(pfFireball, p.Position, p.Rotation);
FireBall fb = (FireBall)fireball.GetComponent(typeof(FireBall));
fb.agressorId = pId;
fb.weaponLevel = p.Controller.WeaponLevel;
networkView.RPC("ShootClientWeapon", RPCMode.All, (int)w, p.PlayerId);
But what if I wanted to let my weapons handle the logic of their appearance/ Instantiation. For Instance if I have a weapon whose gameobject either spawns directly at the position of every player or just the agressor. Fail.
I expected something like...
fireball fb = new FireBall();
fb.gameObject = prefabFireball;
fb.agressorId = pId;
fb.weaponLevel = p.Controller.WeaponLevel;
fb.Fire();
Is there a workaround?
If I make a class not inheriting Monobehaviour then my update method is gone I guess. But the only thing that I need is to handle Instantiation myself.
You could have a FireballBehavior inheriting from MonoBehavior AND a Fireball Object not inheriting from anything.
Your FireballBehavior would take care of spawning and killing Fireballs as well as keeping track of those on scene, on the other hand your Fireball Object should be completely data driven and only hold the data weapon.
Allowing you to send message from the server to the FireballBehavior on any gameObject saying "instanciate that object : Fireball"
Am I clear?
class FireballBehavior : MonoBehavior {
Fireball fireball;
public void startFireball(Fireball frb) {
fireball = frb;
createFireball(); //instanciation and stuff
}
//your functions handling creation update and killing
}
class Fireball {
GameObject gameObject = prefabFireball;
Int agressorId = pId;
Int weaponLevel = p.Controller.WeaponLevel;
}
That way you could just send messages from the network to :
gameobject.GetComponent<FireballBehavior>().startFireball(frbFromServer);
To sum-up : A generic behavior doing the updates and instanciation according to the data and all the data in a small class or struct handled only by the server and sent from it
The advantage with this approach is that it is interchangeable, and as Fireball is a steady state object you can serialize him or store him in database with not too much effort, with just a few changes you could event change Fireball object in FireballBehavior directly during execution to have different fireball at different times...
This is an idea derivated from one of those videos : (dont remember which one... but both are very good to watch)
Unite 2013 Scripting behind the scene
Unite 2013 Internal Unity tips and tricks
To complete the answer you could even have this done a very generic way :
class AnyObjectBehavior : MonoBehavior {
AnyObject Object1;
public void startFireball(anyObject frb) {
Object1 = frb;
initiateAnyObject (); //instanciation and stuff
}
//your functions handling creation update and killing
private void initiateAnyObject () {
myObjectList.add(Object1) //that way you do not have to
// use for loops to edit some objects
//instanciation stuff
}
}
class AnyObject {
//Generic properties
GameObject gameObject = prefabFireball;
}
class Fireball : AnyObject
{
//fireball specific properties
Int agressorId = pId;
Int weaponLevel = p.Controller.WeaponLevel;
}
That way you could just add new classes for any object type you want to instantiate and always use the same behaviorComponent to start update and kill them, and even keep a generic list of all your objects of anyObject type with
List<anyObject> myObjectList = new List<anyObject>() {fireball, carrot, chicken}
A few ideas :
You can have a fake update loop by using delegates on a dummy gameobject that simply invokes the delegate in it's own update loop.
Lack of constructors can be considered an inconvenience that is easily solved with a generic extension method on GameObject. You can for instance have a .Create<T>(params ...) that will essentially hide all the ugliness and do the instantiation and initialization for you.
I use a similar approach and all my weapons are created from 'drop sheets' that are completely random and so on.
You can also Instantiate in your custom serialized class you don't need the FireBallBehaviour.
Best way to do it i found is making a custom serialized Player class that has instances of your Weapon class which has an instance of an Ammo class.
the Player class could look something like this:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class PlayerCharacter
{
//public NetworkPlayer newtWorkPlayer;
public int playerID;
public GameObject characterObject;
public string characterName;
public float walkSpeed;
public int health;
public Vector3 spawnPosition;
public List<Weapon> weapons = new List<Weapon>();
public Weapon equipedWeapon;
public PlayerCharacter()
{
playerID = 0;
characterObject = null;
characterName = "";
walkSpeed = 0;
health = 0;
}
public PlayerCharacter(/*NetworkPlayer nP,*/ int pID, GameObject cO, string cN, float wS, int h, Vector3 sP)
{
//newtWorkPlayer = nP;
playerID = pID;
characterObject = Network.Instantiate(cO, sP, Quaternion.identity, 0)as GameObject;//GameObject.Instantiate(cO,sP,Quaternion.identity)as GameObject;
characterName = cN;
walkSpeed = wS;
health = h;
spawnPosition = sP;
}
public void TakeDamage (int takeDamage)
{
health -= takeDamage;
}
public void Movement (Vector3 target)
{
characterObject.transform.position += target;
}
}
Your Weapon class:
using UnityEngine;
using System.Collections;
[System.Serializable]
public class Weapon
{
public GameObject weaponObject;
public WeaponType typeOfWeapon;
public Ammo ammo;
public Weapon (GameObject wO, WeaponType tOW, Ammo a)
{
weaponObject = wO;
typeOfWeapon = tOW;
ammo = a;
}
public void UseWeapon()
{
switch(typeOfWeapon)
{
case WeaponType.FireBall:
//some weapon code here
break;
case WeaponType.RidiculousHugeGun:
//some weapon code here
break;
case WeaponType.MegaAwesomeMagicPower:
//some weapon code here
break;
case WeaponType.Knife:
//some weapon code here
break;
}
}
}
public enum WeaponType
{
FireBall,
RidiculousHugeGun,
MegaAwesomeMagicPower,
Knife
}
Your Ammo class:
using UnityEngine;
using System.Collections;
[System.Serializable]
public class Ammo
{
public GameObject ammoObject;
public int damage;
public float moveSpeed;
public Ammo(GameObject aO, int d, float mS)
{
ammoObject = GameObject.Instantiate(aO)as GameObject;
damage = d;
moveSpeed = mS;
}
public IEnumerator Movement (Vector3 target)
{
while(ammoObject != null)
{
ammoObject.transform.position = ammoObject.transform.forward+target*moveSpeed*Time.deltaTime;
yield return null;
}
}
}
public enum AmmoType
{
FireBallBall,
RidiculousHugeBullet,
MegaAwesomeMagicPowerEffect,
None
}
Then you would have a PlayerController thats Monobehaviour to handle all the Input and Collision detection:
I only give a movement example here because its getting to much code here already, but i guess you can imagine how you would do the same with the weapons. Since you're weapon is accessible through the player instance as player.currentWeapon or the ammo through player.currentWeapon.ammo
using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour {
public PlayerCharacter player;
void Update ()
{
if(Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0)
{
player.Movement(new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"))*player.walkSpeed*Time.deltaTime);
}
}
}
In multiplayer the player gets his instance from the GameServer:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GameServer : MonoBehaviour
{
public List<PlayerCharacter> players = new List<PlayerCharacter>();
private int playerCount = 0;
void OnPlayerConnected(NetworkPlayer player)
{
networkView.RPC("CreatePlayer", player);
Debug.Log("Player " + playerCount + " connected from " + player.ipAddress + ":" + player.port);
}
[RPC]
void CreatePlayer()
{
playerCount++;
PlayerCharacter playerChar = new PlayerCharacter(/*player,*/ playerCount, (GameObject)Resources.Load("Player"), "Player"+playerCount, 5, 100, Vector3.zero);
playerChar.characterObject.AddComponent<PlayerController>().player = playerChar;
players.Add(playerChar);
}
}
one last thing # Géry Arduino : I don't see how your example is generic at all? It's not typesafe anywhere, since you are committing the actual data type. for reference about generics read this