I have a list of different creatures in a game, each of these animals will be unlocked if a specific condition is reached, of course the simple solution is to use an enum to categorize the creatures, but I want to be able to set the conditions themselves in inspector.
Manager.cs:
public Creature[] creatures;
public void Start()
{
foreach (var creature in creatures)
{
if (creature.condition()) creature.Unlock();
}
}
For example, to unlock Ogre creature, your player maximum life must be above 120, and to unlock Bat, your character must be faster than 80 move speed. One solution that has come to my mind so far is to serialize Func<bool>, But it does not work and I can not change the condition from the inspector. What is the best solution to this problem? Every suggestion is appreciated.
[SerializeField]
public Func<bool> condition = () => (player.maxHealth > 120);
Related
I'm making a game, in which I have various fields that I'd like to set target values for. For example, my Camera class has:
public double zoomLevel
Currently, if the zoomLevel is (say) 1.0 and I'd like to increase it gradually to (say) 2.0, I have the following other fields to support this:
private double targetZoomLevel
private double zoomIncrement
I then have a Camera.SetZoom(double target, double increment) method that sets a desired furure zoom level, and then a Camera.Update() method that moves the current zoom level towards the target level, using the increment.
This all works well enough, but I'd really like to implement the same behaviour for other fields (e.g. camera world position, player size, player position, etc.). Using my current method, I'd need to add 2 additional 'support' fields for each field.
I'm pretty sure that my current solution is a sub-optimal, but not sure how to go about improving this. I was thinking about implementing a Property<T> class that encapulates this behaviour for a value, but not sure how to generalise an Update() method to move the current value towards its target.
Thanks!
What you're describing sounds very much like an animation. Animations are formal concepts in a couple of frameworks (CSS and WPF come to mind).
The goal of an animation is to transition something from one value to the next.
There are a variety of ways to make that transition. Sometimes you want a 2D point to follow a Bézier curve as the curve's t variable linearly changes from 0 to 1 over some period of time. Other times you want a color to transition smoothly from red to blue by going the long way around a color wheel.
Here's an interface that can abstract over that concept:
public interface IAnimation<T>
{
T Current { get; }
void Update(double progress); // progress is a number between 0 and 1
}
For example, if you want a 2D point to animate over a sine wave 800 units wide and 2 units tall:
public sealed class Point2DAnimation : IAnimation<Point2D>
{
public Point2D Current { get; private set; }
public void Update(double progress)
{
Current = new Point2D(progress * 800, Math.Sin(progress * Math.PI * 2));
}
}
There are also a variety of ways to drive the transition. A common way to drive it is to say "I want this animation to happen as smoothly as possible over X seconds". But sometimes you might want the animation to repeat from the beginning a few more times. Or perhaps you want the animation to run forward then backward then forward and so on forever.
We could define an interface to abstract over the different ways that an IAnimation<T> can be driven. And one implementation might internally have a timer that ticks frequently enough to give the illusion of smoothness as progress goes from 0 to 1 in proportion to the amount of time that has passed at the moment of each tick.
But I want to pause here for a moment and ask a question.
What does the consumer of the abstraction need from it?
If all your code needs is read access to a T and a method called Update(), then Property<T> sounds like your ticket:
public sealed class Property<T>
{
readonly Func<T, T> _update;
public Property(T initial, Func<T, T> update)
{
Value = initial;
_update = update;
}
public T Value { get; private set; }
public void Update()
{
Value = _update(Value);
}
}
That class encapsulates a readable value and gives you an Update() method to call. No need for an IAnimation<T> interface or any other fancy trappings. Of course you'll have to instantiate instances of Property<T> with an update delegate that does what you want. But you can add some static methods somewhere to set it up in the most common ways. For example:
public static class PropertyHelpers
{
public Property<double> CreateZoomLevelProperty(double initial, double increment, double target)
{
return new Property<double>(initial, old =>
{
var #new = old + increment;
if (increment > 0 && #new > target || increment < 0 && #new < target)
return target;
else
return #new;
});
}
}
On the other hand if you want to think of the value as a stream of values controlled by something else in the code and you only need to get read access to the values when they arrive, then perhaps IObservable<T> (and the Reactive NuGet package) is what you're after. For example:
public class Camera
{
double _zoomLevel;
public IDisposable UpdateZoomLevel(IObservable<double> zoomLevels)
{
return zoomLevels.Subscribe(zoomLevel => _zoomLevel = zoomLevel);
}
}
Of course then it becomes the responsibility of other code somewhere to come up with an IObservable<double> that publishes zoom levels in the fashion that you desire.
So I suppose my answer can be summed up as this:
It depends.
Disclaimer: Perhaps none of the code above will compile
I am trying to make a list of gameObjects disappear when the player enters a room, the best way I could think about doing it, was changing the material alpha frame by frame. If there is an alternative to this method that is more performatic, please tell! (not that I noticed any bad performance as this seems simple enough)
I expect the player to enter a room and all of the gameObjects in that list to disappear at the same time and at the same rate (as if they were all just one object having their transparency changed in runtime) but what happens is (I've made a video: https://youtu.be/z1pz2Te_hDg ) that some gameObjects change before others, some disappear way faster than expected and just in the end after the others, it all looks weird.
Changing the alpha of only one object at a time seems fine, but since I need to loop through all of the materials, I can't simply put all objects as a child of one gameObject.
I've tried to change the alpha without making a custom shader with an alpha property by accessing the default alpha in the default "HDPR/Lit" shader and it behaves the same.
This problem seems easy, I feel like I am doing something stupidly wrong but I've been at this for a couple of weeks.
Here is my "RoomTransparencyController" script:
bool transparent = false;
public float enhance = 5;
private struct ShaderPropertyIDs {
public int AlphaRef;
}
private ShaderPropertyIDs shaderProps;
public List<GameObject> gameObjectList;
public List<Material> materialList;
void Start() {
foreach(GameObject obj in gameObjectList) {
foreach(Material mat in obj.GetComponent<MeshRenderer>().materials) {
materialList.Add(mat);
}
}
// Cache property IDs
shaderProps = new ShaderPropertyIDs() {
AlphaRef = Shader.PropertyToID("AlphaRef"),
};
}
void Update() {
UpdateChildsAlpha3(transparent);
}
private void OnTriggerEnter(Collider col) {
if(col.tag == "Player") {
transparent = true;
}
}
private void OnTriggerExit(Collider col) {
if(col.tag == "Player") {
transparent = false;
}
}
void UpdateChildsAlpha3(bool transparent) {
foreach(Material mat in materialList) {
mat.SetFloat("AlphaRef", Mathf.Clamp(mat.GetFloat("AlphaRef") + Time.deltaTime * enhance * (transparent ? -1 : 1), 0, 1));
}
}
PS: This script should mainly contain props and specific walls from a room so there is a script in every room with its gameObject list for better organization. So I thought that each room having its script was the best way to organize and replicate.
If you want the objects to be gone, then use a for loop with Destroy() on all the objects, and if you want them to stop being visible, just use SetActive(). If you want them to work, but just stop being visible, you can disable the renderer component.
It will be much better if you try to use some tweens to solve this problem. Like doTween or LeanTween, where they have some better-optimized way to reduce alpha or anything. Try to play around with it.
In my game I have a big catalog of gear: Armors, weapons and shields. The combinations between these can be really immense.
Besides that, the player has the option of switching in-game to a different set of armor-weapon combination. In the end to solve this, I have used the following object structure.
Whenever I switch the weapons, I activate/deactivate the necessary GameObjects. The animations are set in this way:
Now, the problem is creating the animation. I first considered pre-rendering programatically all the combinations, but my catalog is so huge, that it would create 100s, if not 1000s of animations. So I opted for a different solution. Create in playtime the animation, once I knew what gear would the player select. For that, I created a script to take care of that. The problem is that I have been using APIs from UnityEditor, and now I have realized the build will not work. Specifically because of 2 different classes: EditorCurveBinding and ObjectReferenceKeyframe.
This is a couple snippets of how I was using this classes when creating the animations:
static EditorCurveBinding GetEditorCurveBinding(string path = "")
{
EditorCurveBinding spriteBinding = new EditorCurveBinding();
spriteBinding.type = typeof(SpriteRenderer);
spriteBinding.path = path;
spriteBinding.propertyName = "m_Sprite";
return spriteBinding;
}
static ObjectReferenceKeyframe GetKeyframe(float time, Sprite sprite)
{
ObjectReferenceKeyframe keyframe = new ObjectReferenceKeyframe();
keyframe.time = time / FRAMERATE;
keyframe.value = sprite;
return keyframe;
}
Now, the problem with the Curve, I think I managed to solve, replacing it with this code, replacing EditorCurveBinding with AnimationCurve:
AnimationClip clip = ...
AnimationCurve curve = new AnimationCurve();
clip.SetCurve(path, typeof(SpriteRenderer), "m_Sprite", curve);
But I have no idea how to set the sprites for each animation. I thought that using curve.AddKeycould be helpful, but I have seen no way to add a sprite there.
How could I rewrite that code to avoid using UnityEditor?
Full code
Personally, I would completely avoid built-in Animator and Animations, since this tool is for the very narrow purpose of animating a single object in a predefined way (eg: for a cutscene).
Offtopic - performance
Besides that, the player has the option of switching in-game to a different set of armor-weapon combination. In the end to solve this, I have used the following object structure.
As you probably know this is very inefficient memory-wise and will decrease performance (disabled objects still have marginal CPU overhead). And will incread load time and instantiation time of new objects.
Can I use Animator or Animation?
Because Animator has no API to access it's internals and animation type and overall you cannot do pretty mutch nothing with it except from telling it to Play or Stop.
Since I understand that your animation is "Sprite based" and not "Transform based" any sane idea is just inefficient!
Best solution that I vow against is as follows:
Create "trigger points" that you would "animate"
Based on trigger point - change sprite
public class AnimationController : MonoBehaviour
{
public int AnimationIndex;
public Sprite[] AnimationSprites;
public SpriteRenderer SpriteRenderer;
private void Update()
{
SpriteRenderer.sprite = AnimationSprites[AnimationIndex];
}
}
Better solution?
Since we already need to have custom structure that manages our sprites we might want to optimize the whole thing, since we are not using almost any of the features of Animator we could write custom controller that would replace Animator in this case. This should improve performance significantly since Animator is very heavy!
// MonoBehaviour is optional here
public class SpriteRendererAnimationHandler
{
// More fields that would control the animation timing
public Sprite[] AnimationSprites;
public SpriteRenderer SpriteRenderer;
public void OnAnimationUpdate(int index)
{
var resolvedIndex = ResolveIndex(index);
SpriteRenderer.sprite = AnimationSprites[resolvedIndex];
}
private int ResolveIndex(int index)
{
// Resolve animation index to sprite array index
}
}
// One controller per character - or global per game that synchronize all animations to locked FPS 12.
public class AnimationController : MonoBehaviour
{
private List<SpriteRendererAnimationHandler> Handlers = new List<SpriteRendererAnimationHandler>();
public void FixedUpdate()
{
foreach (var handler in Handlers)
{
// Calculate animation index
int calculatedAnimationIndex = ...;
handler.OnAnimationUpdate(calculatedAnimationIndex);
}
}
}
Add a public field named animation index to Tomasz last example, then create the animations in animator as youd normal do for animation pieces, then animate that animation index field. simple if(currentAnimationyion != lastAnimationIndex) to check if need to send to handlers and ya rocking
Thanks for help in advance. Here is a short snippet of the code that I am having an issue with.
GameObject[] allMotor_array;
public List<GameObject> BrokenMotor_list = new List<GameObject>();
void Start()
{
allMotor_array = GameObject.FindGameObjectsWithTag ("Motors");
}
void Update()
{
foreach (GameObject motor in allMotor_array)
{
if(motor.GetComponent<Pump_event>().damaged)
{
BrokenMotor_list.Add(motor);
}
}
}
I have an array of Gameobjects that is created on Start, each of the gameobjects in the array have a script called Pump_event. What I want to do is add the gameobject with a true boolean (damaged) to the list so that I can create a GUI list of all the motors that are damaged (and then take further action on those motors).
With the current code it instantiates the array fine, but when One of the motors boolean changes to true the list tends to continuously add the motor gameobject to the list on each update cycle. So what I want is to figure out a way of adding the gameobject to the list ONCE.
Having it in the update() is probably not the best method but I really am stuck on how to approach this.
G
The Solution to my problem
Thanks for your answers, you all had well thought out responses. I appreciate it. I didn't go with 1 persons method but instead adapted logical approaches found here to work with my script/s.
Here is what I did.
In my pump_event script the events are sorted in a Case and switch as damage increased on the pump the event would escalate. So I added in a section to that script to include "reporting" the damage.
public class Pump_event : MonoBehaviour
//The damage has taken place and event_category=0\\
switch (event_category)
{
case 0:
Master_script.GetComponent<Control_room>().AddtoList (gameObject);
event_category = 1;
break;
I took advice not to insert these types of programing and placed it into its separate class which works out well.
public class Master_script: MonoBehaviour
public void AddtoList(GameObject motor_tobadded)
{
BrokenMotor_list.Add(motor_tobadded);
}
This also eliminated the need on having an array holding all of the pump event controllers as well.
Now the script all works fine. It may not be most efficient but it is doing its job.
Thank you again to all that helped.
In your Pump_event Script you can have a event Action which you register in this snippet and whenever damaged is set true you need to fire the event.
Example:
// in Pump_event Class
public static event Action<GameObject> OnDamagedValueChanged;
private bool _damaged;
public bool Damaged
{
get { return _damaged;}
set
{
_damaged = value;
if(_damaged)
{
if(OnDamagedValueChanged != null)
OnDamagedValueChanged(gameObject);
}
}
}
In your Current Class where you have array of GameObjects:
void OnEnable()
{
Pump_event.OnDamagedValueChanged += HandleOnDamagedValueChanged;
}
void OnDisable()
{
Pump_event.OnDamagedValueChanged -= HandleOnDamagedValueChanged;
}
void HandleOnDamagedValueChanged(GameObject obj)
{
if (!BrokenMotor_list.Contains (obj))
{
BrokenMotor_list.Add (obj);
}
}
Using Actions is a better approach than doing it in Update Method. It is not good for performance to keep checking for a bool in iteration in update method. and try to avoid GetComponent and Find/FindObjectWithTag Methods in Update. It is not good practice. I hope this is helpful.
According to the code you have posted, the problem lies within the fact that the damaged property is never reset. One solution would be to reset this property once you add it to the list, like so:
if(motor.GetComponent<Pump_event>().damaged)
{
motor.GetComponent<Pump_event>().damaged = false;
BrokenMotor_list.Add(motor);
}
However, multiple copies of the same object could still be added to your list if the motor is damaged again.
To go around this, you could use a HashSet. The hash set will allow only one copy of an object to exist within it, thus, if an object is already present is will not be added again.
The catch is that you will need to override the GetHashCode and Equals methods for your GameObject class since these will be used internally by the hash set to place items within itself and identify duplicates.
check if list already contains motor.
if(motor.GetComponent<Pump_event>().damaged)
{
if(BrokenMotor_list.Contains(motor))
{
BrokenMotor_list.Add(motor);
}
}
although on msdn describes how to implement IEquatable in case if you want compare different objects(with different references) https://msdn.microsoft.com/ru-ru/library/vstudio/bhkz42b3%28v=vs.100%29.aspx
if (!BrokenMotor_list.Contains (motor)) {
BrokenMotor_list.Add (motor);
}
You'd better do this after damage event occur by add a delegate.
I have setup a simple shop where when the player clicks to 'BUY' a character, the item gets unlocked and stays saved as unlocked. This is in one scene (Shop scene).
I have a second scene where the character gets unlocked based on this purchase for the player to be able to select (character scene).
I am placing the scripts on empty gameObjects on each scene. It works fine when it is just buying one character. But how do I replicate this for multiple characters.
I could place the scripts on individual buttons and place corresponding gameobjects under the inspector but this would not be practical if I have like a 100 characters.
Please advice how I could make this work across multiple instances. Thought of tagging and that too doesn't seem feasible. I am open for suggestions if there is a better way of doing this. Thanks.
//Class controlling Shop Scene via an empty Gameobject
public class ShopManager : MonoBehaviour
{
private bool unlocked;
public GameObject greyImg;
void Start()
{
unlocked = PlayerPrefs.GetInt("unlocked") == 1;
greyImg.SetActive(unlocked);
}
public void Buy()
{
unlocked = true;
PlayerPrefs.SetInt("unlocked", 1);
PlayerPrefs.Save();
greyImg.SetActive(true);
}
}
This is how the unity setup looks for shop scene. If the item is already bought, the grey image is set to active thus not allowing the user to click the green buy button any more.
When character is unlocked/bought
//Class controlling Character select scene via an empty Gameobject
public class CharacterManager : MonoBehaviour
{
private bool unlocked;
public GameObject greySelect;
void Start()
{
unlocked = PlayerPrefs.GetInt("unlocked") == 1;
}
void Update()
{
if (unlocked)
{
greySelect.SetActive(false);
}
}
}
This is how Unity setup looks for Character select scene. If the character is already unlocked, the grey select image is set to inactive and the orange select button will be visible thus allowing the character to be selected.
when character unlocked
There are probably many ways in which to tackle this problem. Here's one;
You're going to need a separate player prefs entry for each character. So, you'll need a nice way to keep track of the characters you have and their unlock state. Instead of saving 'unlocked' in player prefs, why not create a class that contains unlocked information?
class UnlockedCharacters
{
bool characterAUnlocked = false;
bool characterBUnlocked = false;
bool characterCUnlocked = true;
}
You can then serialize this whole class and save the whole class inside player prefs. Then, when you load your game you can load this class from player prefs to populate your character information. This way, data is managed and is consistent across saved states.
You could go one step further and keep everything relating to your characters inside of a Dictionary whereby your int is an enum referring to a character and the bool is its unlock state. You can then save/load this dictionary again using player prefs.
Inside the class you could have helper methods with your generic gameobject scripts call to keep things nice and encapsulated.
This way, your individual GameObjects which handle specific characters can hold a reference to this enum and you can use your general script to set/modify your data contents based on your enum field that you can then set via your inspector, or in initialisation code for your object.
EDIT FOR COMMENT BELOW - AN EXAMPLE:
Your generic character controller would go on the individual objects, and by changing your CHAR_TYPE the same script will work to unlock multiple characters.
class CharacterManager
{
public enum CHAR_TYPE = { characterA, characterB, characterC }
private Dictionary<CHAR_TYPE, bool> characterUnlockState;
void Start()
{
// Seeding with some data for example purposes
characterUnlockState = new Dictionary<CHAR_TYPE, bool>();
characterUnlockState.Add(CHAR_TYPE.characterA, false);
characterUnlockState.Add(CHAR_TYPE.characterB, false);
characterUnlockState.Add(CHAR_TYPE.characterC, true);
}
public bool IsCharacterUnlocked(CHAR_TYPE character)
{
if (characterUnlockState.Contains(character)) return characterUnlockState[character];
return false;
}
public void UnlockCharacter(CHAR_TYPE character)
{
if (characterUnlockState.Contains(character)) characterUnlockState[character] = true;
}
}
class GenericCharacterController
{
public CHAR_TYPE character;
public CharacterManager manager;
public void UnlockButtonPressed()
{
manager.UnlockCharacter(character);
}
}
There are a number of ways to approach this. You are going to need an ID of some kind for each item in your store. So I would make a StoreItem script that you place on each thing for sale. The StoreItem script would need an ItemID property at the minimum. Then your StoreManager could check if it's unlocked like this:
PlayerPrefs.GetInt(selectedStoreItem.ItemID + "_unlocked");
Also, while this approach would work, it's not recommended. It's possible for playerprefs to be manipulated by the user outside of your game. So they could potentially give themselves free items. I don't know how important that is to you. The better approach would be store this info on a server somewhere and have the game sync up with that to determine what items the player owns.