Creating an Enemy Class - c#

I've never done this before and my true understanding of classes are not that good. However, I plan on mastering it after this project! What I'd like to do is create a class to determine enemy type by TAG: Enemy1 or Boss. (I've already designed a system to randomize the Enemy1 stats so no two will be the same. However, here, I just want to learn how to properly setup enemies stats so here's my code)
using System.Collections;
public class Enemies : MonoBehaviour {
public float MaxHp;
public static float Hp;
GameObject enemy = GameObject.Find("Enemy1");
GameObject boss = GameObject.Find("Boss");
void Awake()
{
AssignStats(enemy, MaxHp);
}
public static void AssignStats (GameObject en, float MaxHp)
{
if (en.tag == "Enemy1")
{
MaxHp = 50;
Hp = MaxHp;
Debug.Log(Hp);
}
if (en.tag == "Boss")
{
MaxHp = 500;
Hp = MaxHp;
Debug.Log(Hp);
}
}
}
This code doesn't seem to work. Why?

Enemy Class : Enemy.cs (not Monobehavior)
using UnityEngine;
[System.Serializable]
public class Enemy
{
public EnemyType EnemyType;
public GameObject EnemyPrefab;
public string EnemyTag;
public int MaxHealth;
public int EnemyDamage;
public Vector3 SpawnPos;
private int _currentHealth;
public void Init()
{
_currentHealth = MaxHealth;
}
public void UpdateHealth(int newHealthValue)
{
_currentHealth = newHealthValue;
}
public void ReceiveDamage(int damage)
{
var updatedHealth = _currentHealth - damage;
UpdateHealth(updatedHealth > 0 ? updatedHealth : 0);
}
}
Enemies Class : Enemies.cs that manage all enemies, randomize between enemies
using UnityEngine;
public enum EnemyType
{
Enemy1,
Enemy2,
Enemy3,
Enemy4,
Enemy5,
Boss
}
public class Enemies : MonoBehaviour
{
public Enemy[] AllEnemies;
//Initial Value
public int NumberOfEnemies = 3;
private void Start()
{
InitEnemies(NumberOfEnemies);
}
public void InitEnemies(int howManyEnemies)
{
for(int i= 0; i < howManyEnemies; i++)
{
var randomIndex = Random.Range(0, AllEnemies.Length - 1);
SpawnEnemy(AllEnemies[randomIndex]);
}
}
public void SpawnEnemy(Enemy enemy)
{
Instantiate(enemy.EnemyPrefab, enemy.SpawnPos, Quaternion.identity);
enemy.Init();
}
}
You can see that I assigned all enemies data in the inspector that come up from the array of Enemy in enemies class, it has Enemy Prefab, position, damage etc.
If you have any question, feel free to ask :)
Cheers!

if I understand corrlcy.
you don't need to pass parameters to AssignStats method because the all you need property in the class.
I would use gameObject.tag to get the current append object tag.
if you append to Enemy1 component you will do gameObject.tag == "Enemy1" condition.
if you append to Boss component you will do gameObject.tag == "Boss" condition.
you just append the script to your role component and tag right tag.
using System.Collections;
public class Enemies : MonoBehaviour {
public float MaxHp;
public float Hp;
void Awake()
{
AssignStats();
}
public void AssignStats ()
{
if (gameObject.tag == "Enemy1")
{
MaxHp = 50;
Hp = MaxHp;
Debug.Log(Hp);
}
if (gameObject.tag== "Boss")
{
MaxHp = 500;
Hp = MaxHp;
Debug.Log(Hp);
}
}
}

I would do it like that :
//enum contains all your enemies
public enum EnemyType
{
Enemy1,
Boss
}
public class Enemies : MonoBehaviour
{
//This will be assigned in the inspector
public EnemyType CurrentEnemyType;
//You don't need them to be public since you are hardcoding them.
private float MaxHp;
private float Hp;
void Awake()
{
AssignStats();
}
public void AssignStats()
{
if (gameObject.CompareTag(CurrentEnemyType.ToString()))
{
if (CurrentEnemyType == EnemyType.Enemy1)
{
MaxHp = 50;
Hp = MaxHp;
Debug.Log(Hp);
}
// instead of doing separated if blocks, you need to do if else for less code execution
else if (CurrentEnemyType == EnemyType.Boss)
{
MaxHp = 500;
Hp = MaxHp;
Debug.Log(Hp);
}
/*
More simplified way instead of the if else, if you assume that all your enemies except the boss have 50 hp.
MaxHp = CurrentEnemyType == EnemyType.Boss ? 500 : 50;
Hp = MaxHp;
Debug.Log(Hp);
*/
}
}
}
Cheers!

Related

OnCollisionEnter() not working, how i can fix it?

I make random generator and me need destroy object if it touch myself or house, i give colliders to objects.
`
using UnityEngine;
public class TouchObject : MonoBehaviour
{
private void OnCollisionEnter(Collision collision)
{
if(collision.gameObject.name == "Structure") // this is tree
{
Destroy(gameObject);
}
if(collision.gameObject.name == "Floor") // this is details of house
{
Destroy(gameObject);
}
}
}
using static System.Random;
using UnityEngine;
public class RandomGenerator : MonoBehaviour
{
[Header("Игровые объекты")] // game object
public GameObject Tree;
[Header("Лимит строений")] // limit for building
public int StuctLimit;
[Header("Рандомные координаты")] // random position
public int randomPosX;
public int randomPosZ;
private void Generating()
{
if(StuctLimit == 0)
{
System.Random randomPos = new System.Random();
for (int i = 1; i <= 1500; i++)
{
randomPosX = randomPos.Next(10, 290);
randomPosZ = randomPos.Next(10, 290);
Instantiate(Tree, new Vector3(randomPosX, 0.0f, randomPosZ), Quaternion.identity);
}
StuctLimit++;
}
}
private void Start()
{
Generating();
}
}
using UnityEngine;
public class HouseGenerator : MonoBehaviour
{
[Header("Строение дома")] // game object
public GameObject House;
[Header("Лимит строений")] // limit for building
public int StuctLimit;
[Header("Рандомные координаты")] // random position
public int randomPosX;
public int randomPosZ;
private void Generating()
{
if(StuctLimit == 0)
{
System.Random randomPos = new System.Random();
for (int time = 0; time <= 20; time++)
{
randomPosX = randomPos.Next(10, 290);
randomPosZ = randomPos.Next(10, 290);
Instantiate(House, new Vector3(randomPosX, 0.5f, randomPosZ), Quaternion.identity);
}
StuctLimit++;
}
}
private void Start()
{
Generating();
}
}
It was worked, but i round position for set block, i use it(Mathf.Round(range))
I use Instantiate(Object, new Vector3(positions), Quaternion.identity) for create structure
Here code for: Check Touch objects; Generating Trees; Generating House.

I am having issues with a wave spawner

I am developing a Tower Defence game and I need a wave spawner. I tried to use Brackeys Wave spawner but it only supports one type of enemy per wave and I tried to make one myself like this:
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] Enemy;
public float Rate = 5f;
}
instead of this:
[System.Serializable]
public class Wave
{
public string name;
public Transform enemy;
public int count;
public float rate;
}
This is the code from the 40min Brackeys video
using UnityEngine;
using System.Collections;
public class WaveSpawner : MonoBehaviour {
public enum SpawnState { SPAWNING, WAITING, COUNTING };
[System.Serializable]
public class Wave
{
public string name;
public Transform enemy;
public int count;
public float rate;
}
public Wave[] waves;
private int nextWave = 0;
public int NextWave
{
get { return nextWave + 1; }
}
public Transform[] spawnPoints;
public float timeBetweenWaves = 5f;
private float waveCountdown;
public float WaveCountdown
{
get { return waveCountdown; }
}
private float searchCountdown = 1f;
private SpawnState state = SpawnState.COUNTING;
public SpawnState State
{
get { return state; }
}
void Start()
{
if (spawnPoints.Length == 0)
{
Debug.LogError("No spawn points referenced.");
}
waveCountdown = timeBetweenWaves;
}
void Update()
{
if (state == SpawnState.WAITING)
{
state = SpawnState.COUNTING;
/*if (!EnemyIsAlive())
{
WaveCompleted();
}
else
{
return;
}*/
}
if (waveCountdown <= 0)
{
if (state != SpawnState.SPAWNING)
{
StartCoroutine( SpawnWave ( waves[nextWave] ) );
}
}
else
{
waveCountdown -= Time.deltaTime;
}
}
void WaveCompleted()
{
Debug.Log("Wave Completed!");
state = SpawnState.COUNTING;
waveCountdown = timeBetweenWaves;
if (nextWave + 1 > waves.Length - 1)
{
nextWave = 0;
Debug.Log("ALL WAVES COMPLETE! Looping...");
}
else
{
nextWave++;
}
}
bool EnemyIsAlive()
{
searchCountdown -= Time.deltaTime;
if (searchCountdown <= 0f)
{
searchCountdown = 1f;
if (GameObject.FindGameObjectWithTag("Enemy") == null)
{
return false;
}
}
return true;
}
IEnumerator SpawnWave(Wave _wave)
{
Debug.Log("Spawning Wave: " + _wave.name);
state = SpawnState.SPAWNING;
for (int i = 0; i < _wave.count; i++)
{
SpawnEnemy(_wave.enemy);
yield return new WaitForSeconds( 1f/_wave.rate );
}
state = SpawnState.WAITING;
yield break;
}
void SpawnEnemy(Transform _enemy)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
Transform _sp = spawnPoints[ Random.Range (0, spawnPoints.Length) ];
Instantiate(_enemy, _sp.position, _sp.rotation);
}
}
And I need to add this to the code to get what I am expecting
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] Enemy;
public float Rate = 5f;
}
But I was not able to, can anyone help me?
Thanks in advance,
Dev
You should look at his code, there will be those lines of code :
IEnumerator SpawnWave(Wave _wave)
{
state = SpawnState.SPAWNING;
for (int i = 0; i < wave.count; i++)
{
SpawnEnemy(_wave.enemy);
yield return new WaitForSeconds( 1f/_wave.rate );
}
state = SpawnState.WAITING;
yield break;
}
And also those lines of code :
void SpawnEnemy(Transform _enemy)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
Transform sp = spawnPoints[ Randon.Range(0, spawnPoints.Length) ];
Instantiate(_enemy, _sp.position, _sp.rotation);
}
So what is the problem, the problem is that the SpawnEnemy() method is using only one type of enemies each wave, using _enemy from class Wave which is in [System.Serializable], so my idea is that we will make another class Wave, same same as his code :
[System.Serializable]
public class Wave
{
public string name;
public Transform[] enemies;
public int count;
public float rate;
}
And change a little bit in SpawnEnemy() method, which I will add Random.Rage() to choose a different transform ( or we can say enemy )
void SpawnEnemy(Transform[] _enemies)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
int randomIndex = Random.Range(0, _enemies.Count());
Transform sp = spawnPoints[ Randon.Range(0, spawnPoints.Length) ];
Instantiate(_enemies[randomIndex], _sp.position, _sp.rotation);
}
If having any trouble when doing, just comment below, yeah
After reading through the existing comments, I think you really only need one additional edit within the SpawnWave code to achieve what you are looking for - Add a foreach loop to loop through each Wave Contents object and spawn the appropriate number and type of enemy
Given your updated objects, with one update to a field name for clarity
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] WaveContents;
public float Rate = 5f;
}
Then you just need to loop through the array of WaveContent and call SpawnEnemy for each one, using the WaveContent.Count for the inner loop.
IEnumerator SpawnWave(Wave _wave)
{
Debug.Log("Spawning Wave: " + _wave.name);
state = SpawnState.SPAWNING;
// Loop through your Wave Contents
foreach (var waveContent in Wave.WaveContents)
{
// Spawn the number and type of enemy for each content object
for (int i = 0; i < waveContent.count; i++)
{
SpawnEnemy(waveContent.enemy);
yield return new WaitForSeconds(1f / _wave.rate);
}
}
state = SpawnState.WAITING;
yield break;
}

I can't Save Multiple PlayerPrefs on my Main Menu

Hi I'm a student in game development, making a game where players can grab certain amounts of coins on each maps, every maps has different type of coins data, like a highscore. But it only saves 1 playerprefs only. Why is that happening?
This is my Scene Management Script;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;
public class changeScene : MonoBehaviour
{
public TextMeshProUGUI desertCoinAmount;
public TextMeshProUGUI plainsCoinAmount;
void Update()
{
desertCoinAmount.text = PlayerPrefs.GetInt("DesertCoins").ToString();
plainsCoinAmount.text = PlayerPrefs.GetInt("PlainsCoins").ToString();
}
public void mainMenu()
{
SceneManager.LoadScene("mainMenu");
PlayerPrefs.Save();
}
public void desertLevel()
{
SceneManager.LoadScene("ancientDesertLEVEL");
Time.timeScale = 1f;
PlayerPrefs.Save();
}
public void plainsLevel()
{
SceneManager.LoadScene("Plain Biome");
Time.timeScale = 1f;
PlayerPrefs.Save();
}
public void jungleLevel()
{
SceneManager.LoadScene("Jungle Biome");
Time.timeScale = 1f;
}
}
And This is my PlayerController;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class PlayerController : MonoBehaviour
{
public GameObject winPopup, losePopup;
public GameObject heart1, heart2, heart3;
public float gravityScale = 10f;
private Rigidbody rb;
public TextMeshProUGUI coinText;
public AudioSource coinSound;
int coin_sumDesert;
int coin_sumPlains;
int life_sum = 3;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
GetComponent<Rigidbody>().AddForce(Physics.gravity * gravityScale, ForceMode.Force);
}
void Update()
{
PlayerPrefs.SetInt("DesertCoins", coin_sumDesert);
PlayerPrefs.SetInt("PlainsCoins", coin_sumPlains);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Coins")
{
coin_sumDesert++;
coinText.text = coin_sumDesert.ToString();
Destroy(other.gameObject);
coinSound.Play();
}
if (other.gameObject.tag == "PlainsCoins")
{
coin_sumPlains++;
coinText.text = coin_sumPlains.ToString();
Destroy(other.gameObject);
coinSound.Play();
}
if (other.gameObject.tag == "finishLine")
{
winPopup.SetActive(true);
}
if (other.gameObject.tag == "obstacles")
{
Debug.Log("Collide Detected");
life_sum--;
if (life_sum == 2)
{
heart1.SetActive(false);
}
else if (life_sum == 1)
{
heart2.SetActive(false);
}
else if (life_sum == 0)
{
heart3.SetActive(false);
losePopup.SetActive(true);
Time.timeScale = 0.0f;
}
}
}
}
I would appreciate the reply (this is my first time using StackOverflow xD)
If you have multiple PlayerController then obviously they write to the same PlayerPrefs keys.
Whenever you save a player's data, make sure you differentiate them e.g. Score1, Score2, etc.
This is a way among many others to achieve it:
The player, very simple, append index to player pref to differentiate among many:
public sealed class Player : MonoBehaviour
{
private const string ChocolateBarsKey = "ChocolateBars";
[SerializeField]
[HideInInspector]
private int Index;
private int ChocolateBars
{
get => GetInt(ChocolateBarsKey);
set => SetInt(ChocolateBarsKey, value);
}
private int GetInt([NotNull] string key, int defaultValue = default)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
return PlayerPrefs.GetInt($"{key}{Index}", defaultValue);
}
private void SetInt([NotNull] string key, int value)
{
PlayerPrefs.SetInt($"{key}{Index}", value);
}
[NotNull]
internal static Player Create([NotNull] GameObject parent, int index)
{
if (parent == null)
throw new ArgumentNullException(nameof(parent));
var controller = parent.AddComponent<Player>();
controller.name = $"{nameof(Player)} {index}";
controller.Index = index;
return controller;
}
}
The factory, scriptable singleton won't lose state on assembly reload, whereas if you'd used a static int for player count, it would reset itself to zero at assembly reload because static fields are not serialized by Unity.
public sealed class PlayerFactory : ScriptableSingleton<PlayerFactory>
{
[SerializeField]
private int PlayerCount;
[NotNull]
public Player Create(GameObject parent)
{
return Player.Create(parent, ++PlayerCount);
}
}
Now if you don't want to store score data within Player, it'll be another pattern. I leave that to you as an exercise.

Is there a ways for cinemachine to retarget player after it is destroyed and instantiated as a clone?

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;

How can I use behaviours in Unity?

I am very new to c# and coding in general.
What I got is a weapon script that has a public int of 50 (damage). Then I got another script which is the enemy health.
Now what I want to do is use the value in the weapon script to apply it to the enemy health script and I have no clue how to do it.
I know its something quite simple but ive been bashing my head against the wall trying to figure this thing out.
Please help!
Weapon.cs:
using UnityEngine;
using System.Collections;
public class Weapon : MonoBehaviour {
static Animator anim;
public GameObject hitbox;
public int damage = 50;
private AudioSource MyAudioSource;
private AudioClip WeaponSound;
void Start () {
anim = GetComponentInParent<Animator>();
MyAudioSource = GetComponent<AudioSource>();
GetComponent<EnemyHealth>().TakeDamage(damage);
}
void Update () {
attack();
block();
}
public void attack() {
if (Input.GetButtonDown("Fire1")) {
GetComponent<EnemyHealth>().TakeDamage(damage);
anim.SetBool("IsAttacking", true);
hitbox.SetActive(true);
Debug.Log("hit");
MyAudioSource.PlayOneShot(WeaponSound);
}
else {
anim.SetBool("IsAttacking", false);
hitbox.SetActive(false);
}
}
public void block() {
if (Input.GetButtonDown("Fire2")) {
anim.SetBool("IsBlocking", true);
}
else {
anim.SetBool("IsBlocking", false);
}
}
}
EnemyHealth.cs:
using UnityEngine;
using System.Collections;
public class EnemyHealth : MonoBehaviour {
public int maxHealth = 100;
private int currentHealth;
private Animator animator;
void Start () {
currentHealth = maxHealth;
animator = GetComponent<Animator>();
}
public void OnTriggerEnter(Collider other) {
other.GetComponent<Weapon>().attack();
}
public void TakeDamage(int _damage) {
currentHealth -= _damage;
animator.SetTrigger("IsHit");
if(currentHealth <= 0) {
Die();
}
}
void Die() {
animator.SetBool("Isdead", true);
Destroy(gameObject);
}
}
Assuming these are both instantiated in another main class (meaning ones not isntatiate from another) in c# you just use the '.' operator to access public elements, properties and functions in a class
main()
{
EnemyHealth myehlth = new EnemyHealth();
Weapon myweapn = new Weapon ();
myehlth.TakeDamage(myweapn.damage);
}
Here I used the '.' operator to access the public damage in your weapon class and then used the '.' oeprator to pass it to the public TakeDamage function in your health class
The answer was quite simple! Thanks noone392
main()
{
Weapon myweapn = new Weapon ();
TakeDamage(myweapn.damage);
}

Categories