Workaround for Constructors in Unity C# - c#

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

Related

Unity3D: How to do object pooling without a spawner singleton

Usually, if you use object pooling, you make a singleton like in this video.
After seeing this video, I discovered how messy singleton can be. Is there any other way to do object pooling without using singletons? I wanna instead use Events.
You would need to hold the pool in a class which is not a singleton, and handle your gameobject pool according to your events.
Regarding to call them with events, "I want to use events" is not a very concrete question. You need to set your events to listen (method subscribe) and to call them in the code wherever they're supposed to occur, this is invoke the method. I suggest that if you are not clear about this, try to use the unity events (OnTriggerEnter, if(Input.GetMouseButtonDown(0)) in the Update etc) until you dig in the topic enough to understand them and make ones of you own with c# events or UnityEvents when needed.
Find two template scripts, a pool and and event handler to handle your objects in the scene. You can check those out in an empty scene with your respective two gameObject to attach, and the object you want in the pool, pressing 'space' and 'A' to create from pool and return to pool respectively.
Pool manager:
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
private Queue<GameObject> objPool;
private Queue<GameObject> activeObj;
private int poolSize = 10;
public GameObject objPrefab;
void Start()
{
//queues init
objPool = new Queue<GameObject>();
activeObj = new Queue<GameObject>();
//pool init
for (int i = 0; i < poolSize; i++)
{
GameObject newObj = Instantiate(objPrefab);
objPool.Enqueue(newObj);
newObj.SetActive(false);
}
}
public GameObject GetRandomActiveGO() {
GameObject lastActive = default;
if (activeObj.Count > 0)
lastActive = activeObj.Dequeue();
else {
Debug.LogError("Active object queue is empty");
}
return lastActive;
}
//get from pool
public GameObject GetObjFromPool(Vector3 newPosition, Quaternion newRotation)
{
GameObject newObject = objPool.Dequeue();
newObject.SetActive(true);
newObject.transform.SetPositionAndRotation(newPosition, newRotation);
//keep actives to be retrieved
activeObj.Enqueue(newObject);
return newObject;
}
//return to pool
public void ReturnObjToPool(GameObject go)
{
go.SetActive(false);
objPool.Enqueue(go);
}
}
Event handler:
using UnityEngine;
public class EventHandler : MonoBehaviour
{
public delegate GameObject OnSpacePressed(Vector3 newPosition, Quaternion newRotation);
public OnSpacePressed onSpacePressed;
public delegate void OnAKeyPressed(GameObject go);
public OnAKeyPressed onAKeyPressed;
public PoolManager poolManager;
void Start()
{
onSpacePressed = poolManager.GetObjFromPool;
onAKeyPressed = poolManager.ReturnObjToPool;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
onSpacePressed?.Invoke(new Vector3(0, 0, 0), Quaternion.identity);
}
//here I get a random active, however this would be called in the specific objects remove circumstances,
//so you should have a reference to that specific gameobje when rerunrning it to the pool.
if (Input.GetKeyDown(KeyCode.A))
{
GameObject go = poolManager.GetRandomActiveGO();
onAKeyPressed?.Invoke(go);
}
}
}
Edit: Singleton pattern
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
protected static T _instance;
public static T instance
{
get
{
if (_instance == null)
{
_instance = GameObject.FindObjectOfType<T>();
if (_instance == null)
{
_instance = new GameObject(typeof(T).Name).AddComponent<T>();
}
}
return _instance;
}
}
}

Unity3D playing sound when Player collides with an object with a specific tag

I using Unity 2019.2.14f1 to create a simple 3D game.
In that game, I want to play a sound anytime my Player collides with a gameObject with a specific tag.
The MainCamera has an Audio Listener and I am using Cinemachine Free Look, that is following my avatar, inside the ThridPersonController (I am using the one that comes on Standard Assets - but I have hidden Ethan and added my own character/avatar).
The gameObject with the tag that I want to destroy has an Audio Source:
In order to make the sound playing on the collision, I started by creating an empty gameObject to serve as the AudioManager, and added a new component (C# script) to it:
using UnityEngine.Audio;
using System;
using UnityEngine;
public class AudioManager : MonoBehaviour
{
public Sound[] sounds;
// Start is called before the first frame update
void Awake()
{
foreach (Sound s in sounds)
{
s.source = gameObject.AddComponent<AudioSource>();
s.source.clip = s.clip;
s.source.volume = s.volume;
s.source.pitch = s.pitch;
}
}
// Update is called once per frame
public void Play (string name)
{
Sound s = Array.Find(sounds, sound => sound.name == name);
s.source.Play();
}
}
And created the script Sound.cs:
using UnityEngine.Audio;
using UnityEngine;
[System.Serializable]
public class Sound
{
public string name;
public AudioClip clip;
[Range(0f, 1f)]
public float volume;
[Range(.1f, 3f)]
public float pitch;
[HideInInspector]
public AudioSource source;
}
After that, in the Unity UI, I went to the Inspector in the gameObject AudioManager, and added a new element in the script that I named: CatchingPresent.
On the Third Person Character script, in order to destroy a gameObject (with a specific tag) when colliding with it, I have added the following:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
}
}
It is working properly as that specific object is disappearing on collision. Now, in order to play the sound "CatchingPresent" anytime the Player collides with the object with the tag, in this case, Present, I have tried adding the following to the if in the OnCollisionEnter:
FindObjectOfType<AudioManager>().Play("CatchingPresent");
But I get the error:
The type or namespace name 'AudioManager' could not be found (are you
missing a using directive or an assembly reference?)
AudioManager.instance.Play("CatchingPresent");
But I get the error:
The name 'AudioManager' does not exist in the current context
As all the compiler errors need to be fixed before entering the Playmode, any guidance on how to make the sound playing after a collision between the player and the gameObject with the tag Present is appreciated.
Edit 1: Assuming that it is helpful, here it goes the full ThirdPersonUserControl.cs:
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityStandardAssets.CrossPlatformInput;
namespace UnityStandardAssets.Characters.ThirdPerson
{
[RequireComponent(typeof (ThirdPersonCharacter))]
public class ThirdPersonUserControl : MonoBehaviour
{
public Text countText;
public Text winText;
private int count;
private ThirdPersonCharacter m_Character; // A reference to the ThirdPersonCharacter on the object
private Transform m_Cam; // A reference to the main camera in the scenes transform
private Vector3 m_CamForward; // The current forward direction of the camera
private Vector3 m_Move;
private bool m_Jump; // the world-relative desired move direction, calculated from the camForward and user input.
private void Start()
{
count = 20;
SetCountText();
winText.text = "";
// get the transform of the main camera
if (Camera.main != null)
{
m_Cam = Camera.main.transform;
}
else
{
Debug.LogWarning(
"Warning: no main camera found. Third person character needs a Camera tagged \"MainCamera\", for camera-relative controls.", gameObject);
// we use self-relative controls in this case, which probably isn't what the user wants, but hey, we warned them!
}
// get the third person character ( this should never be null due to require component )
m_Character = GetComponent<ThirdPersonCharacter>();
}
private void Update()
{
if (!m_Jump)
{
m_Jump = CrossPlatformInputManager.GetButtonDown("Jump");
}
}
// Fixed update is called in sync with physics
private void FixedUpdate()
{
// read inputs
float h = CrossPlatformInputManager.GetAxis("Horizontal");
float v = CrossPlatformInputManager.GetAxis("Vertical");
bool crouch = Input.GetKey(KeyCode.C);
// calculate move direction to pass to character
if (m_Cam != null)
{
// calculate camera relative direction to move:
m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized;
m_Move = v*m_CamForward + h*m_Cam.right;
}
else
{
// we use world-relative directions in the case of no main camera
m_Move = v*Vector3.forward + h*Vector3.right;
}
#if !MOBILE_INPUT
// walk speed multiplier
if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f;
#endif
// pass all parameters to the character control script
m_Character.Move(m_Move, crouch, m_Jump);
m_Jump = false;
}
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
//FindObjectOfType<AudioManager>().Play("CatchingPresent");
AudioManager.instance.Play("CatchingPresent");
}
}
void SetCountText()
{
countText.text = "Missing: " + count.ToString();
if (count == 0)
{
winText.text = "You saved Christmas!";
}
}
}
}
Edit 2: Hierarchy in Unity:
Reformulated the approach that I was following and solved the problem by simply adding an Audio Source to the ThirdPersonController (with the AudioClip that I wanted to call) and added GetComponent<AudioSource>().Play(); to the if statement as it follows:
void OnCollisionEnter(Collision other)
{
if (other.gameObject.CompareTag("Present"))
{
Destroy(other.gameObject);
count = count - 1;
SetCountText();
GetComponent<AudioSource>().Play();
}
}
Importing your scripts myself works without any issues when using FindObjectOfType<AudioManager>().Play("CatchingPresent");. Try reimporting your scripts from the editor (right click in the project folder > reimport all. this might take a while depending on the size of your project)
to use AudioManager.instance.Play("CatchingPresent"); you would first need to create a static variable that holds instance like this (this only works as a singleton, and will break if multiple AudioManager's are in the scene):
public class AudioManager : MonoBehaviour
{
//Create a static AudioManager that will hold the reference to this instance of AudioManager
public static AudioManager Instance;
public Sound[] sounds;
//Assign Instance to the instance of this AudioManager in the constructor
AudioManager()
{
Instance = this;
}
// Rest of the AudioManager code
}
Doing it like this, and using the rest of your code also works for me.

How to achieve awareness of "kill" events in a scene

I have been doing a RPG game in Unity with C # and when doing a system of quests, specifically those of killing a certain number of enemies, I found the problem of having 3 enemies in the scene and being the target of the quest: Kill 3 enemies. If I kill them before activating the quest and later active the quest does not give me the reward (in this case experience). How can I tell the enemies and make that if the quest detects that I have already killed the necessary enemies to get the quest give me the reward equally?
Here the two needed scripts i think:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class QuestObject : MonoBehaviour {
public int questNumber;
public QuestManager qManager;
public string startText;
public string endText;
public bool isItemQuest;
public string targetItem;
public bool isEnemyQuest;
public string targetEnemy;
public int enemiesToKill;
private int enemyKillCount;
private PlayerStats playerStats;
public int EXPToGive;
void Start () {
playerStats = FindObjectOfType <PlayerStats> ();
}
void Update () {
if (isItemQuest) {
if (qManager.itemCollected == targetItem) {
qManager.itemCollected = null;
EndQuest ();
}
}
if (isEnemyQuest) {
if (qManager.enemyKilled == targetEnemy) {
qManager.enemyKilled = null;
enemyKillCount++;
}
if (enemyKillCount >= enemiesToKill) {
EndQuest ();
}
}
}
public void StartQuest (){
qManager.ShowQuestText (startText);
}
public void EndQuest (){
qManager.ShowQuestText (endText);
playerStats.AddEXP (EXPToGive);
qManager.questCompleted [questNumber] = true;
gameObject.SetActive (false);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyHealth : MonoBehaviour {
public int startingHealth;
public int currentHealth;
public GameObject damageBurst;
private PlayerStats playerStats;
public int EXPToGive;
public string enemyQuestName;
private QuestManager qManager;
void Start ()
{
// Setting up the references.
//anim = GetComponent <Animator> ();
//enemyAudio = GetComponent <AudioSource> ();
//enemyMovement = GetComponent <EnemyMovement> ();
//enemyAttacking = GetComponentInChildren <EnemyAttack> ();
// Set the initial health of the player.
currentHealth = startingHealth;
playerStats = FindObjectOfType <PlayerStats> ();
qManager = FindObjectOfType <QuestManager> ();
}
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
public void TakeDamage (int amountDamage)
{
// Reduce the current health by the damage amount.
currentHealth -= amountDamage;
Instantiate (damageBurst, transform.position, transform.rotation);
}
public void SetMaxHelth () {
currentHealth = startingHealth;
}
}
One Aproach would be to create some type of "WorldManager" which counts every Enemy which has been slain. And when Starting a quest this quest could check the WorldManagers kill count and add it to it's own count.
public void StartQuest (){
qManager.ShowQuestText (startText);
this.enemyKillCount += worldManager.GetKillCount();
}
In your enemy class you have to add a kill to your worldManager.
void Update ()
{
if (currentHealth <= 0) {
qManager.enemyKilled = enemyQuestName;
this.worldManager.AddKill(this)
Destroy (gameObject);
playerStats.AddEXP (EXPToGive);
}
}
Alternative:
Make your QManager be aware of every kill in a Scene.
You can achieve this through many ways.
One of them is passing your EnemyObject an reference of your Qmanager and do the same as with the "WorldManager" provided above, or you use Messaging and fire a Message targeting the QManager when an enemy is slain.
Alternative 2:
Throw an Event when an enemy has been slain and subscribe to it on your QManager/WorldManager. This way u can reuse your enemy class in every game. From my point of view static dependencies are evil, but there are many discussions and SO and everywhere on the internet about that.
You can several approach. The most straight-forward is to use static.
The purpose of static is for the variable/method to belong to the class instead of an instance of the class.
In your case, you want each enemy to have its own health, this cannot be static.
And you want to count how many instances there are in the scene from the class. So static is fine.
public class Enemy:MonoBehaviour
{
private static int enemyCount = 0;
public static int EnemyCount {get{ return enemyCount;} }
public event Action<int> RaiseEnemyDeath;
public static void ResetEnemyCount(){
enemyCount = 0;
}
private int health;
public void Damage(int damage)
{
CheckForDamage(); // here you check that damage is not neg or too big...
this.health -= damage;
if(this.health <= 0)
{
OnDeath();
}
}
void OnActivate()
{
enemyCount++;
this.health = 20;
}
void OnDeath()
{
enemyCount--;
RaiseEnemyDeath(enemyCount); // Should check for nullity...
}
}
This one is fairly simple. The first part is all static and is relevant to the class. The second part is relevant to the instance. If you use a pool of enemy and then reuse the same instance multiple times, the OnActivate method is called when you make the enemy alive in the scene (it may have been there for a while as inactive). Then when the health is down, kill the enemy (there are not all the required actions there...) and trigger the event.
Using the public static property, you can know what is the enemy count from a GameManager (Enemy should not affect the gameplay, only takes care of the enemy).
public class GameManager:MonoBehaviour
{
void Start()
{
Enemy.RaiseEnemyDeath += Enemy_RaiseEnemyDeath;
}
void Enemy_RaiseEnemyDeath(int count)
{
if(count < 0){ // End of level }
// You can also access enemyCount
int count = Enemy.EnemyCount;
}
}
The good point of using this principle is that Enemy has no clue about GameManager and can be reused in another game without any modification. The GameManager is a higher level entity and knows about it.

Accessing boolean from a different class in C#

I have one collision script in which I set a boolean to true or false
using UnityEngine;
using System.Collections;
public class IsTriggerLockCamera : MonoBehaviour {
public bool CameraLock = false;
public void OnTriggerStay2D(Collider2D other) {
CameraLock = true;
Debug.Log ("Im inside");
}
public void OnTriggerExit2D(Collider2D other) {
CameraLock = false;
Debug.Log ("I exited");
}
}
I want to access this boolean from my camera script, I tried this
if (CameraLock == true) {
Debug.Log ("Im locked");
}
However, I get an error saying that CameraLock doesn't exist in the current context. The boolean is public so I'm very confused.
EDIT: I feel like I didn't give good enough info so I'll start by posting the whole camera script and then clarifying.
using System;
using UnityEngine;
public class CameraFollowLockY : MonoBehaviour
{
public Transform target;
public float damping = 1;
public float lookAheadFactor = 3;
public float lookAheadReturnSpeed = 0.5f;
public float lookAheadMoveThreshold = 0.1f;
private float m_OffsetZ;
private Vector3 m_LastTargetPosition;
private Vector3 m_CurrentVelocity;
private Vector3 m_LookAheadPos;
private void Start()
{
m_LastTargetPosition = target.position;
m_OffsetZ = (transform.position - target.position).z;
transform.parent = null;
}
private void Update()
{
float xMoveDelta = (target.position - m_LastTargetPosition).x;
bool updateLookAheadTarget = Mathf.Abs(xMoveDelta) > lookAheadMoveThreshold;
if (updateLookAheadTarget)
{
m_LookAheadPos = lookAheadFactor*Vector3.right*Mathf.Sign(xMoveDelta);
}
else
{
m_LookAheadPos = Vector3.MoveTowards(m_LookAheadPos, Vector3.zero, Time.deltaTime*lookAheadReturnSpeed);
}
Vector3 aheadTargetPos = target.position + m_LookAheadPos + Vector3.forward*m_OffsetZ;
Vector3 newPos = Vector3.SmoothDamp(transform.position, aheadTargetPos, ref m_CurrentVelocity, damping);
transform.position = newPos;
transform.position = new Vector3(transform.position.x, 0, transform.position.z);
m_LastTargetPosition = target.position;
if (CameraLock == true) {
Debug.Log ("Im locked");
}
}
The IsTriggerLockCamera is a script I used for an invisible collider in Unity. My camera is focused on the player at all times, but I want it to stop moving when the player is close to reaching the edge of the map, so he can notice that the map is ending. The original plan was, that the collider would send out information when player enters it and then instead of Debug.Log ("Im Locked"); would be some code that would lock the camera in place. I don't know if this solution is very elegant and I'd like to apologize for not clarifying everything properly beforehand, but I started coding probably 2 months ago (I did only Rails websites) and I got into C# game development about a week ago so I'm still missing the terminology required to properly describe the problems I encounter. So far, no suggestion has worked. The closest working suggestion was OnoSendai's suggestion, but apparently it's not allowed to create MonoBehaviour using "new".
Edit2: Making the boolean static didn't work at first, but then I realized that I had to make some changes in my camera script as well, so it works now, but Philip said that it's a bad advice - I personally have no idea why, I assume that it's something like using !important in CSS, you just use it as a last resort because it makes the code not that flexible - so I'm still open to ideas.
You may want to try a full reference to the CameraLock property based on the object instance - as in
var objRef = new IsTriggerLockCamera(); // Just an example of object reference -
// You may already have one
// on your code.
if (objRef.CameraLock) {
Debug.Log ("Im locked");
}
That happens because CameraLock is marked as public, but not as static - it only exists on a instantiated object.
If your camera needs access to the IsTriggerLockCamera then dependency injection is good way to make it clear :
class Camera{
private readonly IsTriggerLockCamera _locker;
public Camera(IsTriggerLockCamera locker){
if (locker== null)
{
throw new ArgumentNullException("locker");
}
_locker = locker;
}
public void whatevermethod(){
if (_locker.CameraLock){
...
}
}
}
You need to set the variable you want to access from other scripts, static as well as public.
using UnityEngine;
using System.Collections;
public class IsTriggerLockCamera : MonoBehaviour {
public static bool CameraLock = false;
public void OnTriggerStay2D(Collider2D other) {
CameraLock = true;
Debug.Log ("Im inside");
}
public void OnTriggerExit2D(Collider2D other) {
CameraLock = false;
Debug.Log ("I exited");
}
}
Then you can access CameraLock :
if (CameraLock == true) {
Debug.Log ("Im locked");
}
I would like to suggest some approach,
Make CameraLock variable as protected.
Create a new class and make IsTriggerLockCamera class as base class.
consume the CameraLock variable and work on it.
Thanks,
C# has a function called { get; set } that you can use with a variable
This program is an example
Static Type
public static class AYO
{
public static string Variable1 { get; set; }
}
public class B
{
public void LOL()
{
string foo;
AYO.Variable1 = "Variable is now set";
foo = AYO.Variable1;
Console.WriteLine(foo);
}
}
Non Static
public class AYO
{
public string Variable1 { get; set; }
}
public class B
{
AYO ayo = new AYO();
public void LOL()
{
string foo;
ayo.Variable1 = "Variable is now set";
foo = ayo.Variable1;
Console.WriteLine(foo);
}
}

Use of addComponent instead of new keyword in unity

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.

Categories