My English skill is poor I'm not a native English speaker.
Please understand.
I want to make the logic that detecting collision
For that, I make a character class. The Character class inherits the MonoBehaviour of the Unity system and has a feature as below.
The class has the container to put the skill was collided with own.
The class has the coroutine to show the status of the container. This coroutine starts when the Character class starts.
The class overrides OnTriggerEnter2D function of the Unity system. In this function, the skill that collides with own is added to the container.
I made the above feature as below code.
public class Character : MonoBehaviour
{
private ConcurrentDictionary<Skill, float> _skills = new ConcurrentDictionary<Skill, float>();
protected virtual void Start()
{
StartCoroutine(Check());
}
private IEnumerator Check()
{
float delayTime = 0.1f;
First:
yield return new WaitForSeconds(delayTime);
foreach (var skill in _skills.Keys.ToList())
{
Debug.Log($"_skills: {skill}");
}
goto First;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag != "cold_wind") return;
var skill = collision.GetComponent<Skill>();
_skills.TryAdd(skill, 100);
}
}
And I added BoxCollider2D component to the prefab that to add the above Character class.
After then I made the Skill class. I omit the Skill class because the Skill class doesn't have a feature yet. I added BoxCollider2D and RigidBody2D components to the prefab that to add the Skill class.
Finally I checked the Simulated function of the RigidBody2D to receive a collision event.
The program executes as the below sequence.
The skill is generated for 3 seconds and it collides with the character.
OnTriggerEnter2D function of the Character was called and skill that collided was added to the container. (Concurrent<Skill, float>)
The Check coroutine shows the skill be included in the container.
Here, I have the problem. Logically I think the Skill class included in the container must not be removed automatically.
But after 3 seconds (the skill effect appears for 3 seconds) if the skill effect disappears then the Skill class included in the container was removed. At the result Check coroutine shows null as below.
Could someone tell me what I did wrong?
Any advice would be much appreciated.
Thanks for reading.
Most likely everything you are doing is working as intended. The one mistake would be unexpected behavior with TryAdd, where the value added can be null.
Inside of your OnTriggerEnter2D function, add a check to determine what you collided with to only check for spells. The easiest way to do this is check the tag of the object. For all of your spells, add a tag that you can check against, then change your collision code to look something like
private void OnTriggerEnter2D(Collider2D collision)
{
if(collision.gameObject.tag == "SpellTagHere")
{
// the issue is this line can result in NULL if another object collides with your player
var skill = collision.GetComponent<Skill>();
// meaning a null is added here
_skills.TryAdd(skill, 100);
}
}
Related
Context
I'm working in a pickup system in my game. I've a component called AbstractSightCollider that has a sphere collider and some AbstractPickupableObject that are the objects meant to be picked up.
AbstractSightCollider is attached to the main character, but could be attached to any alive entity or anything that is able to contain inventory objects.
The way i designed it, it's that when AbstractSightCollider detects an object, it fires an UnityEvent called PickupDetected and when the player leaves the range of pickup, it call an UnityEvent called PickupLeave
The problem
I can't make OnCollisionEnter and OnCollisionExit trigger.
Some code
This is attached to AbstractSightCollider
public class AbstractObjectSight : MonoBehaviour
{
public OnPickupableDetected pickupDetected;
public OnPickupableLeave pickupLeave;
private void OnCollisionEnter(Collision col) {
GameObject gameObject = col.gameObject;
AbstractPickupableObject abstractPickupableObject =
gameObject.transform.GetComponent<AbstractPickupableObject>();
if (abstractPickupableObject != null) {
pickupDetected.Invoke(abstractPickupableObject);
}
}
private void OnCollisionExit(Collision col) {
GameObject gameObject = col.gameObject;
AbstractPickupableObject abstractInventoryObject =
gameObject.transform.GetComponent<AbstractPickupableObject>();
if (abstractInventoryObject != null) {
pickupLeave.Invoke(abstractInventoryObject);
}
}
[System.Serializable]
public class OnPickupableDetected : UnityEvent<AbstractPickupableObject> { }
[System.Serializable]
public class OnPickupableLeave : UnityEvent<AbstractPickupableObject> { }
}
And here are the properties:
Thanks for your time
Make sure both objects (the one with the script and the one that will cause the trigger) have colliders and rigidbodys, I find if one doesn't have those the triggers and collisions will not work.
I just found out the problem.
OnCollisionEnter and OnCollisionExit aren't the events that i needed to listen, because they work with rigidbody. My AbstractSight is that, a non body abstract sphere where the entities are allowed to grab items.
Instead, i used OnTriggerEnter, OnTriggerExit and now it works like a charm.
Rigid body on the parent object means the collider will work on ANY child component but the rigid body MUST be on the parent object.
Example:
MyProjectile has a rigid body and a collider - set isTrigger = true
MyEnemy parent object just holds scripts
MyEnemy childObject holds the enemy prefab including its collider,
Everything is set to isKinematic = true, with no gravity.
Everything works fine.
I have made a scriptable object which has a one property a public sprite. I want to have one version of sprite added everywhere I want.
With the possibility that if I ever want to change it I will just pin another sprite to scriptable object and in every place where it is pinned the sprite will change.
Is there any way to walk thought this problem or any other idea to make one object where I will store one Sprite and pin it to multiple objects.
Finally is this concept possible in Unity?
I have already thought about:
Deriving prom Sprite Renderer but the class is sealed, so i cannot make my own version of it this way
Creating my own custom version of Sprite Render cause if an any update come I would have to make yet again another version.
I think you can use prefabs to achieve the same thing as you need:
You can create prefab by dragging and object to your assets.(Your prefab will be just an object with transform and e.g. Sprite Renderer)
After creating prefab you can copy it as many times as you want.
Than when you decide to change the sprite just simply go to your prefab and edit sprite there. Now every instance of your prefab will have sprite changed.
It is possible with C# events. First, make your ScriptableObject call event whenever sprite is set. Allow Sprite to be set only using property (or method), so that you could track the change, like this:
public sealed class SpriteObject : ScriptableObject
{
public event Action SpriteChanged;
[SerializeField]
private Sprite _sprite;
public Sprite Sprite
{
get { return _sprite; }
set
{
if(_sprite == value)
return;
_sprite = value;
SpriteChanged?.Invoke();
}
}
}
Then, you need script, that will react to changed and assign the changed sprite to SpriteRenderer. So, something like this:
NOTE: name is purely for example. Do NOT name your classes like this!
[RequireComponent(typeof(SpriteRenderer))]
public class ScriptThatUsedSpriteObject : MonoBehaviour
{
public SpriteObject spriteObject;
private SpriteRenderer spriteRenderer;
/// Called once the script is created.
private void Awake()
{
spriteRenderer = GetComponent<SpriteRenderer>();
if(spriteObject != null)
{
spriteRenderer.sprite = spriteObject.Sprite;
spriteObject.SpriteChanged += HandleSpriteChanged;
}
}
/// Called whenever sprite is changed inside SpriteObject.
private void HandleSpriteChanged()
{
spriteRenderer.sprite = spriteObject.Sprite;
}
}
Now, if some script changes sprite in SpriteObject, every ScriptThatUsedSpriteObject will automatically update their sprites.
public class PublicSprite : MonoBehaviour {
public Sprite publicSprite;
#region Singelton
public static PublicSprite instance;
private void Awake()
{
if (instance != null)
{
Debug.LogWarning("More than one instance found");
return;
}
instance = this;
}
}
get the sprite like this:
PublicSprite.instance.publicSprite;
as Mentioned here, you probably want to use a Singelton. It is best to avoid the use of them only if its necessary as its a very bad programming practice. (you can learn more on why to avoid them here: What is so bad about singletons?
However, I can't think of another way of doing what you seek for.
I am struggeling to come up with a smart way to deal with cutscenes in my 2D game. I think I have decided to Create/Control the cutscenes using the Animator, and moving the actual GameObjects around.
What I am struggeling with is:
This approach means each GameObject needs it's own Animator. So how do I then trigger the correct Cutscene? I am going to use game stages to control the game and its progress, so for example:
if(gameStage == 34 && entering scene castle)
{
playCorrectCutscene();
}
How and where do I keep references to all my cutscenes? Say I have 22 NPCs in a scene, and 15 of those have their own Animator since they have a cutscene, how do I play the Animation from NPC_11?
I guess what I am looking for is some sort of "Cutscene Manager". The best possible solution would be to have a List of every cutscene (animation) with an ID. Then I could just use the ID to play the correct animation. But since each Animation is stored in the actual GameObject I have no idea if that is possible.
Hope Im making sense.
Once you create complete prefab with 1 Animator per prefab, then you can use bellow method to create instances on the fly:
Let say you have CutsceneManager script like this:
class CutsceneManager : MonoBehaviour {
public GameObject InstantiateResource(string folder, string name)
{
GameObject resource = Instantiate<GameObject>(Resources.Load<GameObject>(folder + "/" + name));
if (resource == null)
{
Debug.LogErrorFormat("Cannot find resource {0} in {1}", name, folder);
return null;
}
resource.name = name;
return resource;
}
}
The easiest way to use it is to attach it to Empty object. Then you can create public variable for manager in other scripts, where you would need to show cutscene, like this:
class SomeOtherScript : MonoBehaviour {
public CutsceneManager CutsceneManager;
}
This will let you drag&drop CutsceneManager empty into each script where you need it. This way you have 1 instance of cutscenemanager for all classes.
Now, in place where you would need to play custscene, you instantiate:
class SomeOtherScript : MonoBehaviour {
public CutsceneManager CutsceneManager;
public void PlayMyCutscene() {
GameObject cutscene = CutsceneManager.InstantiateResource("Cutscenes", "SomeOtherCutscene");
cutscene.setActive(true); // Or whetever other method to fire it off
}
}
On folder structure you would need to have: Assets\Resources\Cutscenes
Cutscenes would be called: SomeOtherCutscene.prefab
Notice there is no need to include .prefab when you are instantiating one - Unity framework will "know" and add it for you.
I made a pretty basic 2D game to learn. I have 2 Scenes, and switching between them worked great. I used empty gameObjects as Start/Exit point of the Scene, so that the game would know to put player on point X after exiting through point X (for example exit outside house if I walk out the door).
Then I added a "Scene0", to use for persistent general scripts like GameManager, Sounds, Music, etc. With just one object called "Controller" that I DontDestroyOnLoad().
After adding this Scene and then just switching Scenes right away to my MainScene, all of a sudden the game starts acting really strange;
the first time I move from my MainScene (Scene1), to my secondary Scene (Scene2), it works fine, but then when I leave Scene2 to go back to Scene1, the player spawns in the middle of nowhere.
And this ONLY happens if I launch the game from my Persistent Scene.
I have no idea what is wrong, I don't add anything that interferes with my scene transitions, all I've added so far is playerHealth, for testing.
Scripts attached to my (persistent) Controller:
DDOL:
public class DDOL : MonoBehaviour {
// Use this for initialization
void Awake () {
DontDestroyOnLoad (gameObject);
}
}
GameManager:
public class GameManager : MonoBehaviour {
public static GameManager manager;
public int playerMaxHealth;
public int playerCurrentHealth;
void Awake(){
if (manager == null) {
manager = this;
} else if (manager != this) {
Destroy (gameObject);
}
}
// Use this for initialization
void Start () {
SceneManager.LoadScene("test_scene");
}
// Update is called once per frame
void Update () {
}
}
Scripts attached to my StartPoint:
PlayerStartPoint:
public class PlayerStartPoint : MonoBehaviour {
private PlayerController thePlayer;
private CameraController theCamera;
public Vector2 startDir;
public string pointName;
// Use this for initialization
void Start () {
thePlayer = FindObjectOfType<PlayerController> ();
if (thePlayer.startPoint == pointName) {
thePlayer.transform.position = transform.position;
thePlayer.lastMove = startDir;
theCamera = FindObjectOfType<CameraController> ();
theCamera.transform.position = new Vector3(transform.position.x, transform.position.y, theCamera.transform.position.z);
}
}
}
And finally ExitPoint:
LoadNewArea:
public class LoadNewArea : MonoBehaviour {
public string levelToLoad;
public string exitPoint;
private PlayerController thePlayer;
// Use this for initialization
void Start () {
thePlayer = FindObjectOfType<PlayerController> ();
}
void OnTriggerEnter2D(Collider2D other){
if (other.gameObject.name == "Player")
{
SceneManager.LoadScene(levelToLoad);
thePlayer.startPoint = exitPoint;
}
}
}
EDIT:
After moving all my DDOL gameObject to the Pre-Scene, it worked. So, I can assume the fault is inside Player or Cameras Start() functions since when they start in Scene1 they get called every time I enter the Scene (only DDOL).
I tried adjusting their Start()functions like follows:
Original camera:
void Start () {
Debug.Log("Starting camera");
if (!cameraExists) {
cameraExists = true;
DontDestroyOnLoad (gameObject);}
else{
Destroy (gameObject);
}
}
Changed Camera:
void Start () {
DontDestroyOnLoad (gameObject);
}
The exact same changes was made in Player.
Obviously this doesnt work because it creates a new Camera/Player every time I enter Scene1 (btw why does it not try to create them when I enter Scene2?, is it because they start in Scene1?). HOWEVER, the new player/camera do start at the correct position, and if I zoom out I can see the old player/camera at that same wrong position as before. So something weird happens when their Start() is called a second time it seems.
You've now mentioned that you had code something like this,
void Start () {
Debug.Log("Starting camera");
if (!cameraExists) {
cameraExists = true;
DontDestroyOnLoad (gameObject);}
else{
Destroy (gameObject);
}
}
Note that this is unfortunately just "utterly incorrect", heh :)
The issues you mention in the question (preload scenes etc) are just totally unrelated to the problem here.
In Unity if you have a character C that persists between scenes a, b, c as you load those scenes, you must kick-off C in it's own (perhaps otherwise empty) scene, you can not use "a" as a matter of convenience to kick off C.
The pattern is, in each of a, b, c just have a line of code like p = FindObjectOfType<Player>(); which runs when the scene loads, and position C as you wish.
Now, regarding your specific puzzle about the unusual behavior you are seeing.
I understand that you want to know why you are observing what you do.
It is a combination of confusion over the following issues: 1 - difference between Awake and Start, 2 - confusion over script execution order {but see below1} 3 - confusion about Destroy versus DestroyImmediate 4 - Not using Debug.Log enough, and not using gameObject.name in there (it's a common in Unity to be wildly confused about which object is talking in Debug.Log) 5 - where you mention you see the other object "off to the side", it's common to drastically confuse which one is which in such situations 6 - confusion between the computer programming concept of "instantiation" (ie, of a class or object) and "instantiating" (confusingly, it's the same word - utterly unrelated) game objects in nity.
If you fiddle around with all those issues, you'll discover an explanation for the behavior you're seeing!
But it doesn't amount to much; in Unity in the "C .. a b c" example you have to create C separately beforehand.
1 {aside, never fiddle with the script execution ordering system in Unity in an effort to solve problems; it's only there for R&D purposes; however it could in fact help you investigate the behavior at hand in this problem, if you are particularly keen to fully understand why you're seeing what you're apparently seeing}
Use the debugger. Have breakpoints at the relevant spots, like PlayerStartPoint.Start() and LoadNewArea.OnTriggerEnter2D() and check that they are executed
At the right time
The right number of times
With the expected values
This should make you see where things get out of hand.
If you use Visual Studio, install https://marketplace.visualstudio.com/items?itemName=SebastienLebreton.VisualStudio2015ToolsforUnity to be able to debug Unity from within Visual Studio.
If you are not using Visual Studio, you probably should.
Is player persistent between scenes (does he have DontDestroyOnLoad)? If no then this might be the reason - you can either try loading the scenes by using the additive mode or by instantiating the player on scene load in correct position.
I've been sort of teaching myself and sort of learning from Jimmy Vegas on youtube: https://www.youtube.com/channel/UCRMXHQ2rJ9_0CHS7mhL7erg
If you haven't seen those tutorials or don't want to look, one of the things he does is create a small script that destroys a coin when the player collider hits it, but mine isn't working. Code below (a little mis-formatted, sorry, couldn't get it to format correctly):
function OnCollisionEnter (collision : Collision) {
if(collision.gameObject.tag == "coinCollect") {
Destroy(this.gameObject);
}
}
I applied the script to a prefab and placed a bunch of coins around a little area, additionally, I made a capsule collider in a first person controller tagged "coinCollect", and ticked "Is Trigger"
Also, I'm trying to make a teleporter that teleports the first person character from one teleporter to another. Code below:
var warptarget001 : GameObject;
var warptarget002 : GameObject;
function OnTriggerEnter (col : Collider) {
if (col.gameObject.tag == "warp001") {
this.transform.position = warptarget002.position;
} else if (col.gameObject.tag == "warp002") {
this.transform.position = warptarget001.position;
}
}
I have four objects here, two warp pads and two warp targets. The two warp pads are tagged "warp001" and "warp002", respectively and the two warp targets are not assigned anything in the code, but assigned by dragging and dropping an empty object into the Serialized Field the script provides. Both pads have capsule colliders with "Is Trigger" unticked but it doesn't work either way, ticked or unticked.
Can anyone tell me what I might be doing wrong? Thank you.
The script was all correct, my problem was that my parent "FPSController" object didn't have a Rigidbody applied to it and should be the only object (as opposed to the "FirstPersonCharacter" object I had nested inside of it) that the scripts are applied to. That seemed to fix the problem.
The correct code is:
/* coincollect.cs */
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class coincollect : MonoBehaviour {
private int _score;
[SerializeField]
private Text _text;
void OnTriggerEnter ( Collider collision ){
if(collision.gameObject.tag == "coin"){
Destroy(collision.gameObject);
_score++;
_text.text = "Score: " + _score;
}
}
}
and:
/* warp.js */
var warptarget001 : GameObject;
var warptarget002 : GameObject;
function OnTriggerEnter (col : Collider) {
if (col.gameObject.tag == "warp001") {
this.transform.position = warptarget002.transform.position;
}
if (col.gameObject.tag == "warp002") {
this.transform.position = warptarget001.transform.position;
}
}
The only thing I can think of for your first problem is that it shouldn't need IsTrigger ticked. Other than that, it sounds like it should work (unless I'm missing something).
For the second problem you're having (with the warps), I don't think you can use warptarget001 by dragging and dropping objects into the fields. The reason being that what you've dragged into that field isn't the same object instance that's inworld.
You should assign their values through the code (preferably in the Start method), by using GameObject.Find("name") for example. This way, warptarget001 corresponds to the inworld gameobject.