im in the middle of a Tower Defense - style developing in Unity using C#.
So, i have an abstract class for the towers that has an int for the range, the rate of fire, etc.
And then i have a few child classes for the different type of towers.
Im in the doubt of having an attribute of type GameObject in the tower class, that contains the Tower Game Object itself (for spawning the projectile, move the tower if necesary) or having the class with the attributes and then having a controller that controls the tower class atributes and also controls the game object itself(when need to move it, set active or not, etc).
I wish i made my self clear!
Thanks!
EDIT
Im not sure wich option would be better.
Option 1:
public abstract class Tower{
private int health;
...
private GameObject object;
public HealthDown(){
if(health >1){ health -= 1;}
else { object.SetActive(false);}
}
}
Option 2:
public abstract class Tower{
private int health;
...
public HealthDown(){
if(health >1){ health -= 1;}
}
}
And then a script that controlls the tower:
public class Controller : MonoBehaviour {
Tower tower;
...
void Update(){
if(tower.health <= 1){this.gameObject.SetActive(false);}
}
}
I wish now its more clear!
Thanks!
I am assuming you have different models/materials for each tower and therefore I would parameterize these different attack rates etc via a MonoBehavior-derived script (TowerController.cs) attached to each prefab:
Tower1 prefab:
Model = tower
Material = tower1
Script = TowerController.cs
attackRate = 10
projectilePrefab = projectile1
Tower2 prefab:
Model = tower
Material = tower2
Script = TowerController.cs
attackRate = 20
projectilePrefab = projectile2
This follows normal Unity workflow and allows editing via the UI and to be overridden by instances if required.
Therefore neither Option 1 nor Option 2, but closer to Option 2 without class inheritance, and more Unity-conventional.
Here's an example implementation of TowerController allowing the attackRate and the projectile prefab to be set (so you can have different versions for each tower type, set via the UI):
using UnityEngine;
using System.Collections;
public class TowerController: MonoBehaviour
{
public int health = 50;
public int attackRate = 10;
public GameObject projectilePrefab;
public void TakeDamage(int damage)
{
health -= damage;
if (health <= 0) {
gameObject.SetActive(false);
}
}
private void FireProjectile()
{
GameObject projectile = Instantiate(projectilePrefab, transform.position, transform.rotation) as GameObject;
projectile.transform.SetParent(transform.parent, true);
// Add force etc.
}
}
public class Tower : MonoBehaviour {
private int health;
public int Health
{
get { return health; }
set { SetHealth(value); }
}
...
public SetHealth(int value){
health = Math.Max(value, 0);
gameObject.SetActive(health > 1);
}
}
Usage:
Tower tower1 = GameObject.Find("Tower1").GetComponent<Tower>();
// Attack tower 1
tower1.Health--;
Related
Hiya - so i think i'm at a bit of a misunderstanding here with using abstract classes so if it isn't too much trouble i need someone to explain to me where i've gone wrong here;
So i have an abstract class structured like this:
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(Collider2D))]
public abstract class Enemy : MonoBehaviour {
// Physics
protected Rigidbody2D rb;
protected Collider2D col;
protected virtual void Awake(){
// Physics
rb = this.GetComponent<Rigidbody2D>();
col = this.GetComponent<Collider2D>();
col.isTrigger = true;
}
}
Then a class inheriting it:
public class Whizzer : Enemy {
protected override void Awake(){
// Physics
rb = GetComponent<Rigidbody2D>();
rb.bodyType = RigidbodyType2D.Dynamic;
rb.gravityScale = 0f;
col = GetComponent<Collider2D>();
col.isTrigger = true;
}
}
However the problem i'm having is that when i reference rb or col inside the Whizzer class it's (i think) accessing the variables defined in the abstract Enemy class - this results in only one instance of a gameobject with this script working at a time since the last script to run assigns their rigidbody and collider as the variables in the abstract Enemy class
My question is how can i create seperate instances of the variables rb and col for every Whizzer class instance created whilst keeping the variables desired inside the abstract enemy class?
Or if this is even the right way to go about doing this?
Thankyou in advance!
They should be saparate. If you want a variable to be shared among all instances you should use "static" modifier. But you do not use that so each instance of Enemy (Whizzer is also an Enemy) should have it's own value for these fields. How do you know that they point to the same instances of collider and rigidbody?
About your implementation you are slightly missing the point:
public abstract class Enemy : MonoBehaviour {
protected Rigidbody2D rb;
protected Collider2D col;
protected virtual void Awake(){
rb = this.GetComponent<Rigidbody2D>();
col = this.GetComponent<Collider2D>();
col.isTrigger = true;
}
}
Then a class inheriting it:
public class Whizzer : Enemy {
protected override void Awake(){
base.Awake();
rb.bodyType = RigidbodyType2D.Dynamic;
rb.gravityScale = 0f;
}
}
That makes more sense imho, you should use base method in most cases instead of pasting the same chunk of code to override
At the moment I am programming a Unity 2D game. When the game is running the cars start moving and respawn continuously. I added kind of a life system to enable the possibility to shoot the cars. My issue is that my health bar as well as my score board need references to the objects they refer to, but I am unable to create a reference to an object which is not existing before the game starts. Another issue is that I don't know how to add a canvas to a prefab in order to spawn it with the cars continuously and connect them to the car. Is there a way to avoid these conflicts or how is it possible to set the references into prefabs. I will add the code of the spawner, the car and the the scoreboard. Already thank you in advance
Spawner:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour
{
public GameObject carPrefab;
public GameObject enemyCarPrefab;
public GameObject kugel;
public float respawnTime = 10.0f;
public int counterPlayer1=0;
public int counterPlayer2=0;
public int counterEnergy=0;
// Use this for initialization
void Start () {
StartCoroutine(carWave());
}
private void spawnPlayerCars(){
GameObject a = Instantiate(carPrefab) as GameObject;
a.transform.position = new Vector2(-855f, -312.9426f);
counterPlayer1++;
}
private void SpawnEnemyCars(){
GameObject b = Instantiate(enemyCarPrefab) as GameObject;
b.transform.position = new Vector2(853,-233);
counterPlayer2++;
}
private void SpawnEnergy(){
GameObject c = Instantiate(kugel) as GameObject;
c.transform.position = new Vector2(-995,-192);
counterEnergy++;
}
IEnumerator carWave(){
while(true){
yield return new WaitForSeconds(respawnTime);
if(counterPlayer1<3){
spawnPlayerCars();
Debug.Log(counterPlayer1);
}
if(counterPlayer2<3){
SpawnEnemyCars();
Debug.Log(counterPlayer2);
}
if(counterEnergy<3){
SpawnEnergy();
}
}
}
}
Car:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyCar : MonoBehaviour
{
public float speed = 3f;
int zählerAuto1=0;
private Vector2 screenBounds;
public AnzeigePunktzahlPlayer2 points;
public Spawner sp;
public int maxHealth=100;
public int currentHealth;
public HealthBar healthbar;
void Start () {
screenBounds = Camera.main.ScreenToWorldPoint(new Vector2(Screen.width, Screen.height));
points= GetComponent<AnzeigePunktzahlPlayer2>();
sp= GetComponent<Spawner>();
currentHealth=maxHealth;
healthbar.SetMaxHealth(maxHealth);
}
void Update()
{
Vector2 pos = transform.position;
if(pos.x>-855f){
pos = transform.position;
pos.x-= speed* Time.deltaTime;
transform.position=pos;
zählerAuto1++;
}else{
points.counter++;
Debug.Log(points.counter);
sp.counterPlayer2--;
Debug.Log(sp.counterPlayer2);
Destroy(this.gameObject);
}
}
private void OnCollisionEnter2D(Collision2D other) {
if (other.collider.tag=="Kugel"){
takeDamage(40);
//sp.counterPlayer2--;
if(currentHealth<=0)
{
Destroy(this.gameObject);
}
}
}
public void takeDamage(int damage){
currentHealth-= damage;
healthbar.SetHealth(currentHealth);
}
public void getHealed(int heal){
currentHealth+= heal;
healthbar.SetHealth(currentHealth);
}
}
Scoreboard(one part of it(the other one is almost the same)):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class AnzeigePunktzahlPlayer1 : MonoBehaviour
{
public int counter;
public TextMeshProUGUI textPlayer1;
void Start()
{
// counter=0;
textPlayer1= GetComponent<TextMeshProUGUI>();
}
// Update is called once per frame
void Update()
{
textPlayer1.SetText( counter.ToString());
}
}
You could make the health bars and the canvas children of the Car prefab and have them spawn together.
I am trying to do a 2d game and my object doesn't affect the player's lifebar after they collide. The player's healthbar will will get a bigger lifetime but I think something it's wrong with the scripts. (Also the collider of object that needs to be destroy is "is trigger" checked). I put this PowerUp on the object, and the character script and healthbar script on the player.
Character.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityStandardAssets.CrossPlatformInput;
public class Character : MonoBehaviour
{
Rigidbody2D rb;
float dirX;
[SerializeField]
float moveSpeed = 5f, jumpForce = 600f, bulletSpeed = 500f;
Vector3 localScale;
public Transform barrel;
public Rigidbody2D bullet;
// Use this for initialization
void Start()
{
localScale = transform.localScale;
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
dirX = CrossPlatformInputManager.GetAxis("Horizontal");
if (CrossPlatformInputManager.GetButtonDown("Jump"))
Jump();
if (CrossPlatformInputManager.GetButtonDown("Fire1"))
Fire();
}
void FixedUpdate()
{
rb.velocity = new Vector2(dirX * moveSpeed, rb.velocity.y);
}
void Jump()
{
if (rb.velocity.y == 0)
rb.AddForce(Vector2.up * jumpForce);
}
void Fire()
{
var firedBullet = Instantiate(bullet, barrel.position, barrel.rotation);
firedBullet.AddForce(barrel.up * bulletSpeed);
}
}
HealthBar.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class HealthBar : MonoBehaviour
{
public Slider slider;
public Gradient gradient;
public Image fill;
public float health = 100;
public void SetMaxHealth(int health)
{
slider.maxValue = health;
slider.value = health;
fill.color = gradient.Evaluate(1f);
}
public void SetHealth(int health)
{
slider.value = health;
fill.color = gradient.Evaluate(slider.normalizedValue);
}
}
PowerUp.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PowerUp : MonoBehaviour
{
public float multiplayer = 1.4f;
public GameObject pickupEffect;
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Player"))
{
Pickup(other);
}
}
void Pickup(Collider player)
{
//Spawn a cool effect
Instantiate(pickupEffect, transform.position, transform.rotation);
//Apply effect to the player
HealthBar stats =player.GetComponent<HealthBar>();
stats.health *= multiplayer;
// Remove Effect
Destroy(gameObject);
}
}
First of all as others already mentioned for a 2D game with Rigidbody2D and (hopefully) Collider2D components you want to use OnTriggerEnter2D instead!
The Physics and Physics2D are two completely separated engines and don't know each other. A 3D OnTriggerEnter will never get called for 2D physics events like collisions etc.
Then note that
Also the collider of object that needs to be destroy is "is trigger" checked
this is exactly the wrong way round. If your object shall track a OnTriggerEnter then the one that enters (the player) should have isTrigger enabled! In most cases you don't want to do this because the player shall e.g. actually collide with the floor and not fall through it etc.
So what you would need is rather putting an additional script on the player itself that waits for other trigger objects to enter!
Then to be sure either debug your code line by line or add additional logs in order to see what happens:
PowerUp
// put this on the pickup item
public class PowerUp : MonoBehaviour
{
// Make these private, nobody else needs access to those
// (Encapsulation)
[SerializeField] private float multiplayer = 1.4f;
[SerializeField] private GameObject pickupEffect;
public void Pickup(HealthBar stats)
{
//Spawn a cool effect
Instantiate(pickupEffect, transform.position, transform.rotation);
//Apply effect to the player
stats.health *= multiplayer;
// Remove Effect
Destroy(gameObject);
}
}
PowerUpDetector (on the player)
// put on player!
public class PowerUpDetector : MonoBehaviour
{
// reference this via the Inspector
[SerializeField] private HealthBar healthbar;
private void Awake()
{
if(!healthBar) healthBar = GetComponent<HealthBar>();
}
private void OnTriggerEnter2D(Collider2D other)
{
// or whatever tag your powerups have
if (!other.CompareTag("PowerUp"))
{
Debug.LogWarning($"Registered a collision but with wrong tag: {other.tag}", this);
return;
}
var powerup = other.GetComponent<PowerUp>();
if(!powerup)
{
Debug.LogError($"Object {other.name} is tagged PowerUp but has no PowerUp component attached", this);
return;
}
Debug.Log("Found powerup, pick it up!", this);
powerup.Pickup(healthbar);
}
}
Well, and then what you do is only changing the float value
stats.health *= multiplayer;
but you never update the GUI accordingly like you would do when instead using
stats.SetHealth(stats.health * multiplayer)
(Btw: I think you mean multiplier ;) )
I would suggest to rather implement a property like e.g.
public class HealthBar : MonoBehaviour
{
// Make these private, nobody else needs access to those
// (Encapsulation)
[SerializeField] private Slider slider;
[SerializeField] private Gradient gradient;
[SerializeField] private Image fill;
[SerializeField] private float health = 100;
public float Health
{
get { return health; }
set
{
health = value;
slider.value = health;
fill.color = gradient.Evaluate(slider.normalizedValue);
}
}
// be careful with variable names if you have this name already
// for a class field .. was ok this time but might get confusing
public void SetMaxHealth(int value)
{
slider.maxValue = value;
// The property handles the rest anyway
Health = value;
}
}
so now instead of calling SetHealth you simply assign a new value to Health and its setter is automatically execute as well so your GUI is updated.
public void Pickup(HealthBar stats)
{
//Spawn a cool effect
Instantiate(pickupEffect, transform.position, transform.rotation);
//Apply effect to the player
stats.Health *= multiplayer;
Destroy(gameObject);
}
Your qustions are below,
player will get a bigger lifetime
Also the collider of object that needs to be destroy
What situation possibly you could have
Check your Character is Triggered, so it calls 'OnTriggerEnter' and also check tag.
Check your powerUp item that checked true as trigger, 'isTrigger'
Check if collider or hit object scale is tiny, it won't work with collider.
If you have nothing with above check-list, then it should be working
I have an array of prefabs and I want to be able to Instantiate randomly picked prefabs thru Zenject Factory and perform their bindings in their sub-containers.
What I want to do is the same as in this code sample from Zenject documentation, but for randomly selected prefabs.
https://github.com/modesttree/Zenject/blob/master/Documentation/SubContainers.md#using-game-object-contexts-no-monobehaviours
using UnityEngine;
using Zenject;
public class GameInstaller : MonoInstaller
{
[SerializeField]
GameObject ShipPrefab;
public override void InstallBindings()
{
Container.BindInterfacesTo<GameRunner>().AsSingle();
Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
.FromSubContainerResolve().ByNewPrefabInstaller<ShipInstaller>(ShipPrefab);
}
}
I was able to partially make it work with
[SerializeField] private GameObject[] ships;
...
Container.BindFactory<float, ShipFacade, ShipFacade.Factory>()
.FromSubContainerResolve().ByNewGameObjectMethod(SpawnShip);
...
private void SpawnShip(DiContainer container, float speed)
{
container.Bind<ShipFacade>().AsSingle();
container.Bind<Transform>().FromComponentOnRoot();
var shipPrefab = ships[Random.Range(0, ships.Length)];
var ship = container.InstantiatePrefab(shipPrefab);
container.Bind<ShipHealthHandler>().FromNewComponentOn(ship).WhenInjectedInto<ShipFacade>();
container.BindInstance(speed).WhenInjectedInto<ShipInputHandler>();
}
But it's awkward and in this case I guess I'm not using an advantage of sub-container. And also prefabs spawns in an empty GameObject.
What I want to achieve is to be able to use ShipInstaller for sub-container binding.
You're right, there wasn't really a very elegant way to choose the sub-container prefab dynamically.
I took some time to make this better today with this commit. If you use the latest version of Extenject then you can now do things like this:
public class QuxInstaller : Installer {
float _speed;
public QuxInstaller(float speed) {
_speed = speed;
}
public override void InstallBindings() {
Container.BindInstance(_speed);
Container.Bind<QuxFacade>().AsSingle();
}
}
public class CubeInstaller : MonoInstaller
{
public List<GameObject> QuxPrefabs;
public override void InstallBindings()
{
Container.BindFactory<float, QuxFacade, QuxFacade.Factory>()
.FromSubContainerResolve().ByNewPrefabInstaller<QuxInstaller>(ChooseQuxPrefab);
}
UnityEngine.Object ChooseQuxPrefab(InjectContext _) {
return QuxPrefabs[Random.Range(0, QuxPrefabs.Count)];
}
}
My "game" read a file XML, identify some elements and instance him at runtime. This instances is of a prefab.
So, I have a loop with a variable "ins" that create the instances:
ins = (GameObject)Instantiate (this.MyPrefab, position, Quaternion.identity);
I would like, for example, to click on a instance and change its color and not on all instances.
The problem in your code is thatUpdate() calls Click() in every frame. So, whenever you press the mouse button if( Input.GetMouseButtonDown(0) ) becomes true for every prefab and they all process the click event, irrespective of whether they were clicked on or not.
The solution would be to add a Collider component to your prefab and implement OnMouseDown() in ButtonDiagram class to detect mouse clicks on the object. Something like :
public class ButtonDiagram : MonoBehaviour
{
// rest of your code
void OnMouseDown()
{
Debug.Log("Click!");
}
}
Unity's documentation:
http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnMouseDown.html
You could do an array of gameobjects.
Do a foreach loop and add each instance to the your array. Then you could do ins[0].DOSOMETHING or, simpler, have the prefabs you're instantiating have a script on it then accepts mouse clicks or other input and that will affect only the gameobject/prefab that the user interacts with.
using UnityEngine;
using System.Collections;
public class Program : MonoBehaviour
{
private Diagram diagram { get; set; }
public string arquivoXMI;
public GameObject ButtonDiagramGameObject;
private const int SPACEX = 2;
private GameObject ins;
public ArrayList instances{ get; private set; }
// Use this for initialization
void Start ()
{
this.diagram = new Diagram (arquivoXMI);
ButtonForEachSequenceDiagram ();
}
//BUTTON FOR EACH SEQUENCE DIAGRAM
private void ButtonForEachSequenceDiagram()
{
instances = new ArrayList ();
if (this.diagram.SequenceDiagrams.Count > 0) {
float increment = (this.ButtonDiagramGameObject.transform.localScale.x / 2) + SPACEX;
float position = 0;
foreach( Sequence s in this.diagram.SequenceDiagrams )
{
float posBDx = position;
float posBDy = this.ButtonDiagramGameObject.transform.position.y;
float posBDz = this.ButtonDiagramGameObject.transform.position.z;
Vector3 posButtonDiagram = new Vector3 (posBDx, posBDy, posBDz);
ins = (GameObject)Instantiate (this.ButtonDiagramGameObject, posButtonDiagram, Quaternion.identity) ;
ins.GetComponentInChildren<ButtonDiagram> ().NameDiagram ( s.Name );
instances.Add(ins);
position += increment;
}
}
}
// Update is called once per frame
void Update ()
{
foreach( GameObject i in instances ){
i.GetComponentInChildren<ButtonDiagram>().Click();
}
}
}
using UnityEngine;
using System.Collections;
public class ButtonDiagram : MonoBehaviour {
public TextMesh Name;
public GameObject MyCube;
private string nameDiagram;
private float random;
// Use this for initialization
void Start () {
// random = Random.Range(-10.0f, 10.0f);
// NameDiagram = random.ToString();
Name.text = nameDiagram;
}
public void NameDiagram( string nome ){
this.nameDiagram = nome;
}
public void Click(){
if( Input.GetMouseButtonDown(0) )
{
Debug.Log("Click!");
}
}
// Update is called once per frame
void Update () {
}
}