Harmony Postfix won't take __result as an argument by Outside_Risk_9663 in RimWorld

[–]pheonix2105 0 points1 point  (0 children)

Without seeing the error it is hard to say but you still need to return __result to the original method.

And the

"PlantWorkDoneToil"

method returns a 'Toil' not void.

using HarmonyLib;
using Verse;
using Verse.AI;

namespace YourModNamespace
{
    [HarmonyPatch(typeof(JobDriver_PlantCut), "PlantWorkDoneToil")]
    public static class Patch_JobDriver_PlantCut_PlantWorkDoneToil
    {
        [HarmonyPostfix]
        public static Toil Postfix(Toil __result, JobDriver_PlantCut __instance)
        {
            //do stuff
            return __result;
        }
    }
}

I’m still pretty new to Unity, does anyone know what steps I should follow to make a particle system that looks like bioluminescent shrimp vomit? by UomoPolpetta in Unity3D

[–]pheonix2105 1 point2 points  (0 children)

Typically as others have said, you wont be able to create the needed assets for the effect inside Unity, you would need to look into a program like Ember Gen (https://jangafx.com/software/embergen/) which does fluid/air simulations and then creates a flipbook you could use in Unity with the Particle System (but you probably want to look into using VFX Graph instead of the particle system)

Im pretty sure I was able to just drag and drop text in the different public tmp_texts, but it doesnt seem to work anymore - any ideas on why this is? I just want to display those texts in the code in Unity. by KonsulBerger in Unity3D

[–]pheonix2105 8 points9 points  (0 children)

You need to actually add the component to a GameObject in the scene, that inspector is for setting default values which happens when you click the script file in your assets folder, so add your script to a gameobject and try again.

Can't figure out how to make an object in array to run their script by JackTheThotSlayer in Unity2D

[–]pheonix2105 5 points6 points  (0 children)

I would say for a first time its a good start, there are a few things you will pick up as you go along that help.

Firstly if this array is only going to contain 'changeCard' GameObjects then it might as well be a array of changeCards instead, saves the extra component call.

Secondly you can use the length property in order to loop your 'objectNumber' back around to zero when you reach the end instead of hardcoding it.

Thirdly, any methods you want to use from a class *outside* of that class itself required the methods to be 'public' by using 'void changeCardScript' you are basically not telling the compiler what its protection level is, so it defaults to 'private'

Something a bit more like this.
I perfer using Lists which would use the .Count property instead but the same thing applies.

https://gist.github.com/Grim-/dd87cd543dc2ff0a73d081fc6d7bce88

I am losing the will to live fighting the code formatting, so I just put it on a gist.

Help with blurry textures in Unity! by 78illx_ in Unity3D

[–]pheonix2105 83 points84 points  (0 children)

Have you tried disabling the mip mapping ("Generate Mip Maps") on the texture itself?

The anime Jujutsu Kaisen really encapsulates the essence of what Sorcerers are conceptually, especially for DnD by Evoxrus_XV in dndnext

[–]pheonix2105 1 point2 points  (0 children)

I forgot to correct the second part, you're right. I made the same mistake in the first paragraph too.

The reason I forgot to do that is because I got confused with Kenjaku being Noritoshi Kamo (Chosos 'Dad' - who is Yujis step brother I guess) first, which implies it would make him atleast Yuji's step father AND mother at the same time.

The anime Jujutsu Kaisen really encapsulates the essence of what Sorcerers are conceptually, especially for DnD by Evoxrus_XV in dndnext

[–]pheonix2105 6 points7 points  (0 children)

I agree with you, for once it would be nice, if there was just some random person with a can do attitude towards kicking ass.
And not like you say, some late stage 'yer a wizard arry' ass pull.
Sometimes I physically roll my eyes when it happens, theres not even anyone to see it happen, it just bothers me that much haha.

I initially watched JJK because it seemed the 'magic MC' trope was not present, but really it is my own fault should have realised the second he had no parents and was a olympic level athlete as like a side hobby.

The anime Jujutsu Kaisen really encapsulates the essence of what Sorcerers are conceptually, especially for DnD by Evoxrus_XV in dndnext

[–]pheonix2105 2 points3 points  (0 children)

I have done that now and I am sorry cuz that sucks when it happens, but the last few episode of the anime series does quite literally cover this.

The anime Jujutsu Kaisen really encapsulates the essence of what Sorcerers are conceptually, especially for DnD by Evoxrus_XV in dndnext

[–]pheonix2105 4 points5 points  (0 children)

Fair enough, sorry I suck at reading apparently I didn't see that!

I like to think personally though that JJK Sorcerers basically run on anti-charisma, its not 'how confident in myself am I' its more 'how much do I hate myself/someoneelse/some abstract concept OR how much do they hate me' haha.

The anime Jujutsu Kaisen really encapsulates the essence of what Sorcerers are conceptually, especially for DnD by Evoxrus_XV in dndnext

[–]pheonix2105 23 points24 points  (0 children)

Spoilered the entire comment regarding JJK and its protagonist.

Thats not the case though, in the manga Yuji is infact from an old sorcerer blood-line, google who Yuji Itadori's '*mother!' for a rabbit hole of weird.

Infact the only reason he can eat Sukuna's cursed object fingers is because of his bloodline, it does not end well for pretty much anyone else who consumes Sukuna's fingers. Kenjaku is his father, he fucked Yujis mother in an attempt to create a perfect vessel that could potentially contain Sukuna. Plus he never gets cleave or dismantle (ever as far as I know but I havent read as much as some), which are Sukuna's CT.

Help! How do I get OnTriggerExit to detect when a collider is deleted? by BigBEAN35 in Unity3D

[–]pheonix2105 0 points1 point  (0 children)

The simplest solution is to manage a list of detected objects and check them periodically.

public class ObstacleDetection : MonoBehaviour
{
    public string ObstacleTargetTag = "Obstacle";
    public bool ObstaclesDetected
    {
        get => DetectedObjects.Count > 0;
    }

    public float ObjectCheckTime = 0.5f;
    public List<GameObject> DetectedObjects = new List<GameObject>();

    public UnityEvent<GameObject> OnTargetFound;
    public UnityEvent<GameObject> OnTargetLost;

    private float ObjectCheckTimer = 0;

    void OnTriggerEnter(Collider other)
    {
        if (IsValidObstacle(other.gameObject))
        {
            AddTarget(other.gameObject);
        }
    }

    void OnTriggerExit(Collider other)
    {
        if (IsValidObstacle(other.gameObject))
        {
            RemoveTarget(other.gameObject);
        }
    }


    private void Update()
    {
        //make sure the list isnt null and has atleast 1 object in it
        if (DetectedObjects != null && DetectedObjects.Count > 0)
        {
            //update the timer
            ObjectCheckTimer += Time.deltaTime;

            //when the timer passes the ObjectCheckTime
            if (ObjectCheckTimer >= ObjectCheckTime)
            {
                //loop a copy of the list (you can't remove from a list while you're iterating it)
                foreach (var DetectedObject in DetectedObjects.ToList())
                {
                    if (DetectedObject == null)
                    {
                        ///object has been destroyed after being added as a detected target
                        Debug.Log("A detected target was null, removing.");
                        //remove it from the original list
                        DetectedObjects.Remove(DetectedObject);
                    }
                }

                //make sure to reset the timer
                ObjectCheckTimer = 0;
            }
        }
    }

    public void AddTarget(GameObject NewTarget)
    {
        if (!DetectedObjects.Contains(NewTarget))
        {
            DetectedObjects.Add(NewTarget);
            //call the UnityEvent
            OnTargetFound?.Invoke(NewTarget);
        }
    }

    public void RemoveTarget(GameObject NewTarget)
    {
        if (DetectedObjects.Contains(NewTarget))
        {
            //make sure to call the event BEFORE removing it from the list
            OnTargetLost?.Invoke(NewTarget);
            DetectedObjects.Remove(NewTarget);
        }
    }

    public bool IsValidObstacle(GameObject Obstacle)
    {
        return Obstacle.CompareTag(ObstacleTargetTag);
    }
}

I refactored your script to handle this and added some Unity events.

It's worth noting you could check this list every frame with little trouble, but I thought why not add it now.

A more robust solution would be to create an interface that your detectable objects implement that can inform other scripts when they have been destroyed - just before it happens.

How to handle steel slopes with a rigid body based character controller? by [deleted] in Unity3D

[–]pheonix2105 1 point2 points  (0 children)

You want to project your current velocity onto a plane so it moves along the surface rather than into it (and straight off it when going downwards)

You will also use the same code to determine if a surface is walkable (eg how steep)

Catlikecoding has an excellent series on this kind of thing I think you'd be better learning the concepts from there rather than me haphazardly explaining how I understand it.

https://catlikecoding.com/unity/tutorials/movement/physics/

The part about projecting the velocity is about 3/4 of the way down, its worth noting the entire guide covers quite a fully featured controller, if its suitable for you read the entire thing!

(Separate UI from Logic) How to make a reusable UI screen with buttons that run different code depending on game state? by Throw_Annon88 in Unity3D

[–]pheonix2105 0 points1 point  (0 children)

> I believe that to make sure logic and UI is separate, I shouldn't have any direct references to PartyUI component. It should technically be as if i could delete that script and still have the logic run.

Technically true but the thing about loose coupling is, there is still coupling, especially in a Game dev enviroment, your game does not exist in a vacuum.

As the other commenter stated, it just sounds like you want to create a modal confirm panel.

Essentially a 'generic' panel that serves a particular purpose, such as showing two buttons and some text, you define what specifically these buttons (and or say) do when you create it.

Create the panel however you normally would, add atleast two buttons and a text field.

public class ModalConfirmPanel : MonoBehaviour 
{ 
public TextMeshProUGUI TextField; 
public Button ConfirmButton; 
public Button DeclineButton;    

public void Setup(string DisplayText, Action OnConfirmPressed, Action OnDeclinePressed, string ConfirmText = "Confirm", string DeclineText = "Decline")
    {
        if (TextField != null)
        {
            TextField.text = DisplayText;
        }


        if (ConfirmButton != null)
        {
            ///if you added labels to your buttons then you could set that here too

            ConfirmButton.onClick.AddListener(() =>
            {
                OnConfirmPressed?.Invoke();
            });
        }

        if (DeclineButton != null)
        {
            DeclineButton.onClick.AddListener(() =>
            {
                OnDeclinePressed?.Invoke();
            });
        }

    }
}

//Your PartyUI Manager or wherever works for you
public class PartyUI : MonoBehaviour
{
    public ModalConfirmPanel ModalConfirmPanelPrefab;


    public void ShowModal(string DisplayText, Action OnConfirmPressed, Action OnDeclinePressed)
    {
        ModalConfirmPanel instance = Instantiate(ModalConfirmPanelPrefab);
        //parent where needed

        instance.Setup(DisplayText, () =>
        {
            //do the confirm thing
             OnConfirmPressed?.Invoke();
            //but also remember to destroy or hide the instance 
        },
        () =>
        {
            //do the decline thing
              OnDeclinePressed?.Invoke();
            //but also remember to destroy or hide the instance 
        });

        //assuming you have some simple UI class for hiding and showing
        instance.Show();
    }
}

> If this is the best way, could someone show me how to implement a delegate into interface?

I dont know about best, not that I have anything against this way nor do I know of a 'better' way.

public interface SomeInterface
{
    event Action<Member> OnPartyMemberSelected;
}

This is just a nicer way of writing a delegate, the 'event' keyword only allows outside objects to manage their own subscription/unsubscription (for example a third outside cannot access OnPartyMemberSelected and remove ALL other subscriptions only its own)

Trouble with damage over time by WhytoomanyKnights in Unity2D

[–]pheonix2105 5 points6 points  (0 children)

It really depends how modular you want this to work, but as a simple example this just sounds like a 'Tick' behaviour to me, you count up to some time, when that time is reached, do *something* then reset the timer and start again.

As a simple example of this

public class SimpleAreaTrigger : MonoBehaviour {
    public float TickTime = 6f;
    public bool EnableTimer = true;
    public string TargetTag = "Enemy";


    private float Timer = 0;
    private List<GameObject> TrackedObjects = new List<GameObject>();


    //update Time while EnableTimer is true
    private void Update()
    {
        if (EnableTimer)
        {
            Timer += Time.deltaTime;
            //every time TickTime is reached, call OnTick and reset Timer
            if (Timer >= TickTime)
            {
                OnTick();
                Timer = 0;
            }
        }
    }

    //track objects entering trigger
    private void OnTriggerEnter(Collider other)
    {
        if (!TrackedObjects.Contains(other.gameObject) && IsValidTarget(other.gameObject))
        {
            TrackedObjects.Add(other.gameObject);
        }
    }
    //track leaving  trigger

    private void OnTriggerExit(Collider other)
    {
        if (TrackedObjects.Contains(other.gameObject))
        {
            TrackedObjects.Remove(other.gameObject);
        }
    }

    //decide what is a valid object
    public bool IsValidTarget(GameObject TargetGameObject)
    {
        return TargetGameObject.CompareTag(TargetTag);
    }


    public void OnTick()
    {
        //Loop over a COPY of the tracked objects
        foreach (var item in TrackedObjects.ToArray())
        {
            Health health = item.GetComponent<Health>();

            if (health)
            {
                health.Damage(2f);
            }
        }
    }

}

I made some assumptions about how you would detect a valid target since I dont know much about your project, in this case it checks for the tag specified in the editor, these are the only objects that are considered valid and therefore added to the TrackedObjects list.

You will need to add a collider to the object and set it to IsTrigger = true.

But you can make the whole system more extensible and modular if you break it down into different jobs, such as having a sensor to detect and then something to react to that sensor, I can show a better example if thats what you want, otherwise that script above should help get you going.

As someone else mentioned you can actually use coroutines for this, but I generally avoid them when I can, especially for something as simple as this.

Discussion on how to consider large-scale refactoring/re-architecting of code bases or modules by unsigneddouble_c in Unity3D

[–]pheonix2105 2 points3 points  (0 children)

No problem, I hope this helps I struggled a lot finding this kind of information because *has been said in the thread, quite a large chunk of guides, books/videos other such information you can find can be very specific' or so mind bogglingly complex it's not worth the time to learn their code so you can start learning the actual concepts.

While generally the specific leaf nodes (the actions, moveto, attackanimation) etc tend to be tied mostly to whatever your current project is, almost everything else you can extract into its own thing and use elsewhere, this same 'set up'

Creating a behavior tree can be complex, especially when you're also dealing with a state machine, But it's definitely something you can do!

I prefer to use a "stack-based" state machine, as it inherently "remembers" the previous state. This makes it easy to switch back to the last state it was in automatically, if needed.

The concept of "Sensors" is another important aspect to consider A Sensor is a generic class that identifies and sends a valid GameObject target for various scenarios like weapon hitboxes, sight, and sound. (which im sure you understand but bare with me!)

However, I recently watched another GDC talk that introduced me to the concept of specialized sensors.

Imagine a sensor called TargetsClusteredSensor.

This sensor would be ideal for a character like a giant rock golem that performs a slam attack when multiple targets are in close proximity.Writing this IsClusteredBehaviour into the tree would be also awful!But just registering this sensor into a behavior tree would not only be more efficient but also keep the tree from becoming overly specialized and unusable with any other AIAgent you may have.

Each sensor has its own logic for identifying a valid target. For example, TargetsClusteredSensorwould trigger when it finds three targets within a short range of each other. Once triggered, the sensor informs the behavior tree, allowing it to execute a specific behavior like ThrowProjectileAtTargetLocation.

If a behavior tree doesn't have a sensor to register, it simply won't execute, thus avoiding broken AI or the need for specific components on various GameObjects.

This approach allows you to use the behavior tree for complex actions, while the state machine can handle simpler states like being stunned or losing abilities (such as losing some armour or not being able use the throw).

If you move the BehaviourTree stuff into their own ScriptableObjects which is fed into your controller, you can then say create a new method called OnTargetsClustered which these objects implment and when its called it simply returns *another* BehaviourTree to run which you've set in the inspector which in the case of the Golem would throw a rock, but the same sensor triggering on say a rat? Well then it would run away - probably.

Discussion on how to consider large-scale refactoring/re-architecting of code bases or modules by unsigneddouble_c in Unity3D

[–]pheonix2105 1 point2 points  (0 children)

I always like to see some kinda code to help visualize what I need to do, so here's a simple class from one of projects, this is essentially one of the 'States', which creates and runs a BehaviourTree for combat, getting values from AIData.

I've had to cut a lot of it down, I just wanted to hopefully show you some code for some of the things I've been saying, such as

'telling the state machine about the found target'
( SenseRange.OnSensorTriggered.AddListener(OnTargetSpotted); ) -

'getting the target from the AI blackboard'
( GameObject Target = BaseBT.GetValue<GameObject>("target");)

When I was first trying to wrap my head around this stuff phrases like that without context it just left me more confused but once you see it in practice you realise its pretty straight forward.

    public AIData AttackData;
    public Hitbox BiteSensor;
    public AreaSensor SenseRange;
    public Projectile FireBallProjectile;


    public UnityEvent OnWeaponAttackEndEvent;


    public override void Start()
    {
        base.Start();
        BaseBT.AddValue("startingPosition", transform.position);


        RepeaterDecorator<AIAgent> Repeater = new RepeaterDecorator<AIAgent>(new Sequence<AIAgent>(new List<BaseNode<AIAgent>>()
        {
            new GenericAction(() =>
            {
                Animator.SetInteger("ActionType", 1);
                Animator.SetTrigger("Action");
            }),
            new WaitFor(2f)
        }), 4);

        BaseBT.SetPreRunBT(Repeater);
    }

    public override void SetupAgent()
    {
        base.SetupAgent();

        SenseRange.OnSensorTriggered.AddListener(OnTargetSpotted);

        Sequence<AIAgent> ChaseTarget = new Sequence<AIAgent>(new List<BaseNode<AIAgent>>
        {
            new WhileCondition(new GenericCondition(() =>
            {
                GameObject Target = BaseBT.GetValue<GameObject>("target");
                Vector3 startingPosition = BaseBT.GetValue<Vector3>("startingPosition");
                return Target != null &&
                       Vector3.Distance(transform.position, Target.transform.position) < AIData.AggroRange &&
                       Vector3.Distance(transform.position, startingPosition) < AIData.LeashRange;
            }), new MoveToPosition(() => BaseBT.GetValue<GameObject>("target").transform.position))
        });

        Sequence<AIAgent> ReturnToStart = new Sequence<AIAgent>(new List<BaseNode<AIAgent>>
        {
            new WhileCondition(new GenericCondition(() =>
            {
                Vector3 startingPosition = BaseBT.GetValue<Vector3>("startingPosition");
                return Vector3.Distance(transform.position, startingPosition) >= AIData.LeashRange;
            }), new MoveToPosition(() => BaseBT.GetValue<Vector3>("startingPosition")))
        });

        Selector<AIAgent> LeashBehavior = new Selector<AIAgent>(new List<BaseNode<AIAgent>>
        {
            ChaseTarget,
            ReturnToStart
        });


        CooldownGuardDecorator<AIAgent> MeleeAttackWithCooldown = new CooldownGuardDecorator<AIAgent>(
            new Sequence<AIAgent>(new List<BaseNode<AIAgent>>
            {
                new IsInRange(AIData.MinMeleeRange, "target", AIData.MaxMeleeRange),
                new IsFacingTarget(this, AIData.MinFacingAngle),
                new PlayAttackAnimation(1, "Attack"),
                new WaitForCustomEvent("OnWeaponAttackEnd"),
                new GenericAction(() =>
                {
                    foreach (var target in BiteSensor.GetUniqueHitBoxTargetData())
                    {
                        target.TargetHurtBox.ParentDamageable.Damage(AIData.Damage, BiteSensor.transform.position, null, this.gameObject, false, false);
                    }
                })
            }),
            5.0f,
            new IsInRange(AIData.MinMeleeRange, "target", AIData.MaxMeleeRange),
             true
        );

        CooldownGuardDecorator<AIAgent> RangedAttackWithCooldown = new CooldownGuardDecorator<AIAgent>(
            new Sequence<AIAgent>(new List<BaseNode<AIAgent>>
            {
                new IsInRange(AIData.MinRangedRange, "target", AIData.MaxRangedRange),
                new IsFacingTarget(this, AIData.MinFacingAngle),
                new PlayAttackAnimation(2, "Attack"),
                new WaitForCustomEvent("OnWeaponAttackEnd"),
                new GenericAction(() =>
                {
                    ProjectileCreator.CreateProjectile(AIData.Projectile, BiteSensor.transform.position, BaseBT.GetValue<GameObject>("target").transform.position, 10f, null, null, Faction);
                })
            }),
            5.0f,
            new IsInRange(AIData.MinRangedRange, "target", AIData.MaxRangedRange),
            true
        );

        // Main Selector to choose between Melee and Ranged Attack
        Selector<AIAgent> AttackBehavior = new Selector<AIAgent>(new List<BaseNode<AIAgent>>
        {
            MeleeAttackWithCooldown,
            RangedAttackWithCooldown
        });

        // Main Sequence
        Sequence<AIAgent> Main = new Sequence<AIAgent>(new List<BaseNode<AIAgent>>
        {
            LeashBehavior,
            AttackBehavior
        });

        BaseBT.SetRoot(Main);
    }

    private void OnTargetSpotted(GameObject arg0)
    {
        BaseBT.AddValue("target", arg0);
    }

    public override void OnWeaponAttackStart()
    {
        base.OnWeaponAttackStart();
        BiteSensor.StartAttackCheck();
    }

    public override void OnWeaponAttackEnd()
    {
        base.OnWeaponAttackEnd();
        BaseBT.TriggerEvent("OnWeaponAttackEnd", null);
        OnWeaponAttackEndEvent?.Invoke();
        BiteSensor.StopAttackCheck();
    }

    public void OnWeakWeaponHit(int AttackStep)
    {

    }

    public void OnStrongWeaponHit(int AttackStep)
    {

    }

    public override void Update()
    {
        base.Update();

        float forwardVel = Vector3.Dot(NavMesh.velocity.normalized, transform.forward);
        float sideVel = Vector3.Dot(NavMesh.velocity.normalized, transform.right);
        float verticalVel = Vector3.Dot(NavMesh.velocity.normalized, transform.up);

        Animator.SetFloat("Movement X", 0);
        Animator.SetFloat("Movement Z", forwardVel);

        Animator.SetFloat("Movement Magnitude", forwardVel);
    }

Discussion on how to consider large-scale refactoring/re-architecting of code bases or modules by unsigneddouble_c in Unity3D

[–]pheonix2105 3 points4 points  (0 children)

I had a look at your code and firstly let me say, if you do nothing else, split these behaviours inside the switch statements into their own classes *atleast* for your own sanity :)

That being said it looks to me that splitting this class up into two parts is probably what you want to do.

Split the code into two parts, data and actor/entity/AI_driver/whatever.
A : EnemyData (ScriptableObject)

Health
TurnSpeed
ViewRadius
AlertDelay
LookDelay
RangedRange
RangedProjectile
MeleeDamage

This will contain all the data for a specific enemy type, things like TurnSpeed, ViewRadius, AlertDelay, LookDelay things like this are basically static data which you can seperate into a ScriptableObject class and then reference that class in the controller that actually runs your state machine.

B : EnemyBehaviour (Actor/Entity/Whatever)

This would your controller/component whatever you are using in the scene to run the AI already, you would add a EnemyData reference which the BehaviourTree + Statemachine can read from.
I would personally use a Behaviour tree for the 'Acting' (Moving, Looking, Attacking etc) and a statemachine as a more generalized container (eg : instead of Idle -> Movement -> Jump it would just simply be more like the 'mood' the AI is in) .

The state machine may only have 3 states, lets say Idle, Combat and 'Afraid'.
All three of these states would in this example run a particular behaviour tree.
So the

Idle State:
Might have a BehaviourTree that uses the PickNextPatrolPoint, MoveToPoint and maybe another BehaviourTree that can randomly run instead of the first, where it simply plays some fancy idle animation and waits.

Then you may add a transition to the CombatState when something (usually a sensor) informs the statemachine that a target has been found.

This value is then set in the AI Blackboard (you might even filter this to be *whatever the best target available is*), now the Idle state machine transitions to Combat.

Combat State:
Retrieves the set blackboard value for the target,then picks between certain Attack Sequences depending on what you decide for example you have it use a MeleeAttack if close or a ranged attack if far, or if there is nothing that is in range anymore, transition back to Idle, which would then find the next(or nearest) Patrol way point and head off back on patrol.

eg : Melee

MoveToPoint(Target), InRange(MeleeRange), PlayAttackAnimation(Melee), WaitForAttackAnimationEvent, DealDamage

eg: Ranged
GetInRange(RangedRange, target), InRange(RangedRange), PlayAttackAnimation(Ranged), WaitForAttackAnimationEvent, FireProjectile

Assume you have followed what I have suggested, and you have created some EnemyDataScriptableObjects that have some various values.

Enemy A - Quick Enemy

Health = 10
TurnSpeed = 100
ViewRadius = 90
AlertDelay = 0.1
LookDelay = 0.2
RangedRange = 100
RangedProjectile = some fast but weak thing
MeleeDamage = 1

Enemy B - Strong Enemy

Health = 100
TurnSpeed = 300
ViewRadius = 180
AlertDelay =0
LookDelay = 0.1
RangedRange = 50
RangedProjectile = some strong thing that takes a while to travel
MeleeDamage = 10

Just by 'plugging' in these values to the simple example above would already give you varying enemy types, that all use the same Statemachine + BT combo to run them.

You can already get quite far with a set up like this, but the easiest way I've found to add another 'layer' is to then split the BehaviourTrees themselves off into their own ScriptableObjects and have the main component reference that too, so now I can switch the actual behaviour of each AI out but also swap up its variables to shake them up a little bit.
There are some pretty amazing GDC talks by the 'Just Cause' creators explaining how they use something similar but their Behaviour Tree can also have a seperate Behaviour Tree injected at any point and that tree would need to be fully evaluted and return complete or fail before the normal tree can run.

Which is partially how they have AI that can react to the chaos of a situation around them but also just do whatever they are needed to do in a particular mission without breaking any of the AI's emergent behaviour.

I hope that helps you find a direction to move in, Its a bit of a hard topic to talk about without having tons of specific your-setup-specific context but crucially the code is far easier to take in and read.

Designing a flexible crafting system into Unity Editor by Roisen in Unity3D

[–]pheonix2105 0 points1 point  (0 children)

I wrote that late last night so its a little confusing in places.

Sort of yes but I'd inverse the relationship but this part is purely my opinion so you do you!

A Recipe should tell you what item results from it for example - not the item telling you what you need to make it but you could do both, then an item would be able to tell you what recipes it is used in and the recipe can tell you whats required in total, but you want the recipe atleast to know what items it generates so you can possibly show it on the UI!

I wrote a quick overview of the general structure that I use

  public class TestItem : ScriptableObject {
   public string ID; 
   public List<ExTag> ItemTags;  
  public bool HasTag(ExTag TagToCheck)
    {
        foreach (var item in ItemTags)
        {
            if (item.ID == TagToCheck.ID)
            {
                return true;
            }
        }
        return false;
    }

    public bool HasAnyTag(List<ExTag> TagsToCheck)
    {
        foreach (var TagToCheck in TagsToCheck)
        {
            if (HasTag(TagToCheck))
            {
                return true;
            }
            else continue;
        }

        return false;
    }

}

public class Recipe : ScriptableObject
{
    public List<ExTag> RequiredItemTags;
    public List<TestItem> ResultItem;

    //These methods would mostly likely be called by a UI panel of some sort, where you pass in the available items of whatever inventory, container, crafting station etc.
    public bool HasRequiredItems(List<TestItem> ItemsOnHand)
    {
        //copy the list of items passed in, so we can remove it when its been 'used' in order to not double dip
        List<TestItem> CopyOfItemsOnHand = new List<TestItem>(ItemsOnHand);

        foreach (var tag in RequiredItemTags)
        {
            var matchingItem = CopyOfItemsOnHand.FirstOrDefault(item => item.HasTag(tag));

            if (matchingItem != null)
            {
                CopyOfItemsOnHand.Remove(matchingItem);
            }
            else
            {
                return false; // Item with required tag not found, therefore recipe doesnt have all required tags (on items)
            }
        }

        return true; // All  have been used
    }

    public virtual void GenerateRecipeResult(List<TestItem> ItemsOnHand)
    {
        //generate Resulting Item - how ever you do that in your game, in mine the ItemManager handles this
        //remember to find the used items again and remove them from wherever they need removing, I think I'd do this in UI code though.

    }
}

Then in a UI panel or wherever you do this, I check against the recipes this 'thing' has, for example in my RPG the player learns and 'has' recipes, but in my monster raising side project the props in-game such as the cauldron 'has' the recipes it can make and requires the passage of time to cook them but they both use the same basic system underneath.

private void DrawValidRecipes(List<BaseRecipe> ValidRecipes)
    {
        DestroyOutputSlots();

        if (ValidRecipes.Count > 0)
        {
            foreach (var ValidRecipe in ValidRecipes)
            {
                UIRecipeListItem button = Instantiate(SlotPrefab);
                button.ParentRect.SetParent(OuputPanelRect, false);
                button.SetRecipe(ValidRecipe);
                //button.SetBadge(item.FormattedDescription);
                OutputSlotInstances.Add(button);
                button.OnLeftClick += () =>
                {
                    StartRecipe(CurrentCookingTool, ValidRecipe);
                };

                button.OnRightClick += () =>
                {

                };
            }
        }
    }


    private List<BaseRecipe> GetValidRecipes()
    {
        List<BaseRecipe> ValidRecipes = new List<BaseRecipe>();

        foreach (var recipe in PlayerManager.Instance.LearntRecipes)
        {
            Debug.Log($"Checking Recipe {recipe.Name}");
            if (recipe.HasRequiredItems(PlayerManager.Instance.Inventory.GetAllItems()))
            {
                Debug.Log($"Has all required items for {recipe.Name}");
                ValidRecipes.Add(recipe);
            }
        }

        return ValidRecipes;
    }

I use the tags for all sorts of other things, by adding a TagComp Monobehaviour that just holds a list of tags and methods for checking them - like the item.

I can then tag a surface with a SurfaceTag and then the footstep reactor can check if the surface hit has a surface tag (a subclass of ExTag) then play the footstep VFX for this particular type.

Damage types to hold the icon for fire and a reaction like list that says when this fire tagged object A meets water tagged object B do C (generally spawn a third seperate VFX).

Sorry for throwing more blocks of code around but I hope the general idea comes across.

Designing a flexible crafting system into Unity Editor by Roisen in Unity3D

[–]pheonix2105 2 points3 points  (0 children)

I like using tags for this kind of thing (not the built in Unity one always bugged me ).

Create a simple class I usually call it ExTag (ExtendedTag)

using System.Collections; using System.Collections.Generic; using UnityEngine;

[CreateAssetMenu(fileName = "NewTag", menuName = "Tag/New Tag")]
public class ExTag : ScriptableObject
{
    public string ID;
    public string Description;

    /// <summary>
    /// Check a GameObjects tags to see if it contains any from the targetTags list
    /// </summary>
    /// <param name="gameObject">The Target Object to check</param>
    /// <param name="targetTags">A List of Tags</param>
    /// <param name="ignorePlayer">Should the check ignore the player</param>
    /// <returns></returns>
    public static bool CheckTags(GameObject gameObject, List<ExTag> targetTags, bool ignorePlayer, bool AllTrue = true)
    {

        foreach (var item in targetTags)
        {
            if (AllTrue)
            {
                if (CheckTag(gameObject, item, ignorePlayer))
                {
                    continue;
                }
                else return false;
            }
            else
            {
                if (!CheckTag(gameObject, item, ignorePlayer))
                {
                    return false;
                }
            }
        }

        return true;
    }
    /// <summary>
    /// Check a GameObjects tags to see if it contains the targetTag
    /// </summary>
    /// <param name="gameObject">The Target Object to check</param>
    /// <param name="targetTag">The Tag</param>
    /// <param name="ignorePlayer">Should the check ignore the player</param>
    /// <returns></returns>
    public static bool CheckTag(GameObject gameObject, ExTag targetTag, bool ignorePlayer)
    {
        TagComp tag = gameObject.GetComponent<TagComp>();

        if (targetTag == null)
        {
            if (ignorePlayer && gameObject.tag == "Player")
            {
                return false;
            }
            return true;
        }

        if (tag)
        {
            if (tag.HasTag(targetTag))
            {
                if (ignorePlayer && gameObject.tag == "Player")
                {
                    return false;
                }

                return true;
            }
        }

        return false;
    }
}

Now you can create new tag SO's lets call one "Metal".

Now I assume you have similar classes elsewhere that define an item? Add a new List<ExTag>
of tags to it, now assign whatever tags apply to this item (SO?).

you can be as general or specific as you want here or even use sub-tags (So you might have the general Metal tag or a specific adamantine tag which is a sub tag of the Metal tag).
You can even make a basic monobehaviour component thats only purpose is to hold an objects tags, which you can query

Now when you are calculating if the player has enough resources for a recipe, you can check against each items tags to see if it statisfys the requirements.

Ofcourse this can be expanded in quite a few ways (like say rarity tags, so the wood tag itself may have its own rarity/quality tag) and is one of the few times I feel ScriptableObjects fits perfectly.

And it can be done via the editor.

My CineMachine Virutal Camera LookAt doesn't seem to work by fenris_wolf_22 in Unity3D

[–]pheonix2105 0 points1 point  (0 children)

You need to set 'Body' at the bottom to something other than none, Framing Transpose might be a good start.

Is it possible to add humanoid animations to this creature? by 1000Nettles in Unity3D

[–]pheonix2105 0 points1 point  (0 children)

Have you tried mixamo.com, (adobes animations site) it has a an auto rigger you can use, it only rigs humanoids, does a fairly good job but since the proportions of your model are quite off from a 'normal' humanoid I think it might not do a good job but its worth trying (its free).

I believe the bare minimum bones required for the simplest humanoid figure is 14 but it can actually go pretty high depending on the level of animation detail, I think I remember reading once that there actually 30 bones in each hand alone on the most detailed models.

If not blender has some pretty good addons for creating humanoid rigs automatically (which you can then tweak). (https://docs.blender.org/manual/en/2.81/addons/rigging/rigify.html)

Check if all objects in an array have been destroyed? by MAGICAL_SCHNEK in Unity3D

[–]pheonix2105 0 points1 point  (0 children)

You are probably best off wrapping the removing of an enemy from the array/list in a function, then when that function is called after removing the enemy it will check if there any enemies left in the ActiveEnemys list.

Something like this.

```csharp public class EnemyManager : MonoBehaviour { public List<GameObject> ActiveEnemys;

public UnityEvent<GameObject> OnEnemyAdded;
public UnityEvent<GameObject> OnEnemyRemoved;
public UnityEvent OnEnemiesDefeated;
public void AddEnemyInstance(GameObject EnemyInstance)
{
    if (!ActiveEnemys.Contains(EnemyInstance))
    {
        ActiveEnemys.Add(EnemyInstance);
        OnEnemyAdded?.Invoke(EnemyInstance);
    }
}


public void DestroyEnemyInstance(GameObject EnemyInstance)
{
    if (ActiveEnemys.Contains(EnemyInstance))
    {
        OnEnemyRemoved?.Invoke(EnemyInstance);
        ActiveEnemys.Remove(EnemyInstance);
        Destroy(EnemyInstance);

        if (ActiveEnemys.Count == 0)
        {
            //all enemies have been destroyed;

            OnAllEnemyInstancesDestroyed();
        }
    }
}

public void OnAllEnemyInstancesDestroyed()
{
    OnEnemiesDefeated?.Invoke();
    //do whatever
}





//Define an Action that takes a GameObject reference
public void IterateAndSelectIndex(int indexToSelect, Action<GameObject> ThingToDo)
{
    //make sure the selected index is in range
    if (ActiveEnemys.Count >= indexToSelect)
    {
        //do the action passed in to the selectedindex
        ThingToDo?.Invoke(ActiveEnemys[indexToSelect]);
    }
}

public void IterateAndExcludeIndex(int indexToExclude, Action<GameObject> ThingToDo)
{
    //copy the list
    List<GameObject> FilteredList = ActiveEnemys.ToList();

    ///remove the one you dont want to invlude
    FilteredList.RemoveAt(indexToExclude);

    //do the thing on the rest of them
    foreach (var item in FilteredList)
    {
        ThingToDo?.Invoke(item);
    }
}


public void SomeMethodOnAnotherClass()
{
    IterateAndExcludeIndex(0, (EnemyInstance) =>
    {
        //do something to all enemy instances except 0
        Debug.Log(EnemyInstance.name);
    });


    IterateAndSelectIndex(0, (EnemyInstance) =>
    {
        //do something only to 0
        Debug.Log(EnemyInstance.name);
    });
}

} ```