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);
}
Related
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.
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'm currently developing within Unity 2018 and have made a script for decreasing a character's health on collision with an enemy:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HealthManager : MonoBehaviour
{
public static int currentHealth;
public Slider healthBar;
void Awake()
{
healthBar = GetComponent<Slider> ();
currentHealth = 100;
}
void ReduceHealth()
{
currentHealth = currentHealth - 1;
healthBar.value = currentHealth;
}
void Update()
{
healthBar.value = currentHealth;
}
}
When I try to use said method in the scripting file for the enemy I get an error stating "Assets/Custom Scripts/BeetleScript.cs(46,28): error CS0122: `HealthManager.ReduceHealth()' is inaccessible due to its protection level"
The following is the enemy script initiating the variables being used and calling the method:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BeetleScript : MonoBehaviour
{
Animator animator;
public GameObject cucumberToDestroy;
public bool cherryHit = false;
public float smoothTime = 3.0f;
public Vector3 smoothVelocity = Vector3.zero;
public PointsManager _ptsManager;
public HealthManager _healthManager;
void Start()
{
animator = GetComponent<Animator>();
}
void Update()
{
if (cherryHit)
{
var cm = GameObject.Find("CucumberMan");
var tf = cm.transform;
this.gameObject.transform.LookAt(tf);
// move towards Cucumber Man
animator.Play("Standing Run");
transform.position = Vector3.SmoothDamp(transform.position, tf.position,
ref smoothVelocity, smoothTime);
}
}
// Collision Detection Test
void OnCollisionEnter(Collision col)
{
if (col.gameObject.CompareTag("Player"))
{
_healthManager = GameObject.Find
("Health_Slider").GetComponent<HealthManager>();
_healthManager.ReduceHealth();
if (!cherryHit)
{
BeetlePatrol.isAttacking = true;
var cm = GameObject.Find("CucumberMan");
var tf = cm.transform;
this.gameObject.transform.LookAt(tf);
animator.Play("Attacking on Ground");
StartCoroutine("DestroySelfOnGround");
}
else
{
animator.Play("Standing Attack");
StartCoroutine("DestroySelfStanding");
}
}
}
}
Any help to fix this would be appreciated.
Your methods are private.
You have to write public in front of the method you want to access from outside the class.
public void ReduceHealth()
{
...
}
You need to make void ReduceHealth() to be public -> public void ReduceHealth()
The playerLife variable doesn't update visibly in the Inspector or the on-screen Health Text, but the Player still dies because the playerLife drops below zero.
I've determined that the Player prefab attached to the Zombie GameObject is solely the Player prefab rather than the in-scene active Player. How do I make it so that the zombies always reference the in-scene active Player rather than the basic Player prefab, by script? (Also, it won't allow me to manually drag the active Player into the Zombie)
Call hierarchy for playerLife
public class Player : MonoBehaviour
{
public RaycastHit hit;
public int gunDamage = 1;
public Zombie zombie;
private float hitForce = 100f;
public float playerLife;
private Vector3 flareLower = new Vector3(0, -0.5f, 0);
void Start()
{
spawnPoints = playerSpawnPoint.GetComponentsInChildren<Transform>();
playerLife = 200;
}
void Update() //T-toggle
{
if (Input.GetButton("Fire1"))
{
LazerBeam();
}
if (reSpawn != lastToggle)
{
ReSpawn();
reSpawn = false;
}
else
lastToggle = reSpawn;
}
public void Life (float damage)
{
playerLife -= damage;
if (playerLife <=0)
{
playerLife = 100;
SceneManager.LoadScene(2);
}
}
}
public class Zombie : MonoBehaviour
{
public int currentHealth;
public Player player;
public PlayerLifeCollider playerCollider;
private int damage;
public void Damage(int damageAmount)
{
currentHealth -= damageAmount;
if (currentHealth <= 0)
{
PlayerLifeCollider.instance.ObjectsInRange.Remove(gameObject);
DestroyZombie();
}
}
public void DestroyZombie()
{
Destroy(gameObject);
// gameObject.SetActive(false);
}
public void DamagePlayer(float damage)
{
player.Life(damage);
}
}
As you said, the problem is that you are not referencing the Player object on your scene, but a prefab one. To avoid that, you can add a Start function to the Zombie script and ask to look for what should be the only Player instance in the scene. For this, you can use the FindObjectOfType function:
void Start()
{
player = FindObjectOfType<Player>();
}
Considering you will only have one Player script in your entire scene, what you can also do is to save in your Player class a static reference to your Player instance.
public class Player : MonoBehaviour
{
private static Player _instance;
public static Player Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<Player>();
}
return _instance;
}
}
// Reset of your class
}
You can then get this reference in your Zombie script:
public class Zombie : MonoBehaviour
{
static Player player;
void Start()
{
if(player == null)
{
player = Player.Instance;
}
}
// Rest of your class content
}
This way, you will only have one call to the FindObjectOfType function instead of once per object using the Zombie script.
public void Damage(int damageAmount)
{
currentHealth -= damageAmount;
print(currentHealth);// will show in terminal if thats what you are asking
if (currentHealth <= 0)
{
PlayerLifeCollider.instance.ObjectsInRange.Remove(gameObject);
DestroyZombie();
}
}
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);