Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411283 Posts in 69325 Topics- by 58380 Members - Latest Member: bob1029

March 29, 2024, 02:05:35 AM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsPack [ Diablo + Pokémon ]
Pages: 1 2 [3]
Print
Author Topic: Pack [ Diablo + Pokémon ]  (Read 6269 times)
arturoblanco
Level 0
***


View Profile WWW
« Reply #40 on: July 22, 2020, 12:05:10 AM »

Some people have asked me about the transformation effects, in particular how to "gather" particles in the vertex of the creature's geometry.

I use custom vertex streams to direct the particles to the positions I want, and I have made this video explaining how (I stutter a lot but the video has subtitles)


Logged
arturoblanco
Level 0
***


View Profile WWW
« Reply #41 on: August 22, 2020, 02:54:30 PM »

This month I focused on creating the consumable items and also the shortcut bar to hold them and use them during the game.

Since this was the third drag a drop operation (after the equipment screen and the crafting menu) I decided to try to make something more flexible that I could reuse in the future. In those two previous occasions, I wrote an script in each case to control the interaction. One of the reasons was that I wanted to implement several control modes, not only drag&drop but also "click to grab" and "click to socket into an slot".

I did plenty of copy paste but also plenty of modifications (for example, in the equipment system anything that is equiped on a creature gets removed from the inventory, while when crafting the ingredients are only removed when you actually craft), so now that I had to do it a third time and maybe would have to make it again in the future, I thought of isolating that behaviour and make a script that would fit all the cases using the two I already had as a template.

Then the overengineering started. I worked on this script for two days:
Quote
public interface IHasIcon
{
    Sprite GetIcon();
}

public class SelectOrDragIntoSlot<ContentType,SlotType>
    where SlotType : Slot<ContentType>
    where ContentType : IComparable, IHasIcon
{
    private enum AssignationState
    {
        None,
        SelectedSlot,
        SelectedItem,
    }

    public delegate void ContentSlotted(SlotType slot, ContentType content);
    public event ContentSlotted ContentSlottedEvent;
    public event ContentSlotted ContentRemovedFromSlotEvent;

    private ContentType selectedContent;
    private SlotType selectedSlot;
    private AssignationState state;
    private GameObject draggingItem;

   
    private void IngredientEventsUnsubscribe()
    {
        IngredientSlot.SlotClickedEvent -= SlotClick;
        IngredientSlot.SlotRightClickedEvent -= SlotRightClick;
        ScrapableDisplay.ScrapableClickedEvent -= ContentClick;

    }

    private void IngredientEventsSubscribe()
    {
        IngredientSlot.SlotClickedEvent -= SlotClick;
        IngredientSlot.SlotRightClickedEvent -= SlotRightClick;
        ScrapableDisplay.ScrapableClickedEvent -= ContentClick;
    }
   

    /// <summary>
    /// Try to put content into slot
    /// </summary>
    private void SetSlotContent(SlotType slot, ContentType content)
    {
        if ( slot.ValidateContent(content) )
            slot.SlotContent(content);
    }

    public void SlotClick(SlotType slotClicked)
    {
        if (state == AssignationState.SelectedItem)
        {
            SetSlotContent(slotClicked, selectedContent);
            Deselect();
        }
        else
        {
            SelectSlot(slotClicked);
        }
    }

    public void ContentClick(ContentType contentClicked)
    {
        if (state == AssignationState.SelectedSlot)
        {
            SetSlotContent(selectedSlot,contentClicked);
            Deselect();
        }
        else
        {
            SelectContent(contentClicked);
        }
    }

    public void SlotRightClick(SlotType slotDisplay)
    {
        slotDisplay.Unslot();

        Deselect();
    }

    private void SelectContent(ContentType newSelectedContent)
    {
        Deselect();
        selectedContent = newSelectedContent;

        draggingItem = CreateDragGraphic(newSelectedContent);
        state = AssignationState.SelectedItem;
    }

    private void SelectSlot(SlotType newSelectedSlot)
    {
        Deselect();
        selectedSlot = newSelectedSlot;

        ContentType slotContent = selectedSlot.GetContent();
        if (slotContent.Equals(default) == false)
            draggingItem = CreateDragGraphic(slotContent);

        state = AssignationState.SelectedSlot;
    }

    private void Deselect()
    {
        selectedContent = default;

        selectedSlot = default;

        GameObject.Destroy( draggingItem );

        state = AssignationState.None;
    }

    private GameObject CreateDragGraphic(ContentType item)
    {
        GameObject draggedItemGO = new GameObject("DraggedObject");

        draggedItemGO.AddComponent<RectTransform>();
        draggedItemGO.AddComponent<Image>();
        DraggedItem draggedItem = draggedItemGO.AddComponent<DraggedItem>();
        draggedItem.SetImage(item.GetIcon());

        return draggingItem.gameObject;
    }

    private void Update()
    {
        if ((Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1)) &&
             (state != AssignationState.None))
        {
            if ((EventSystem.current.currentSelectedGameObject == null) ||
                 (state == AssignationState.SelectedItem && EventSystem.current.currentSelectedGameObject.GetComponent<SlotType>() == null) ||
                 (state == AssignationState.SelectedSlot && (EventSystem.current.currentSelectedGameObject.GetComponent<ContentType>() == null && EventSystem.current.currentSelectedGameObject.GetComponent<InventoryPanel>() == null)))
            {
                Deselect();
            }
        }
    }
}

And I just though to myself "this can't be right". So I watched a youtube tutorial. I had already watched one before starting the first drag and drop system, but I grew it into a monstrosity. Tehy just used the drag and drop interfaces and I decided to do the same. It is true that it is not very flexible and I lost "the click to grab, click again to drop" but I gained sanity. I  think I may add those controls again if I find a simple way to implement them.

Here is the dragging and dropping and using items on creatures in action:



Logged
Earworm
Level 0
**


View Profile WWW
« Reply #42 on: September 07, 2020, 04:29:40 PM »

Wow, what an awesome take on action RPGs. Having packs of minions that you can customize and level up seems so much fun. It's like playing as a necromancer in Diablo or Last Epoch but with so much more flexibility. Plus so far the look and feel of the game is a departure from the normal gothic and gory look that the genre is known for.

I saw that you had put a bit of effort into sound effects. Did you write music for the game as well? Do you have an idea where you'll be drawing inspiration from on the soundtrack?
Logged
arturoblanco
Level 0
***


View Profile WWW
« Reply #43 on: September 19, 2020, 05:28:13 PM »

Wow, what an awesome take on action RPGs. Having packs of minions that you can customize and level up seems so much fun. It's like playing as a necromancer in Diablo or Last Epoch but with so much more flexibility. Plus so far the look and feel of the game is a departure from the normal gothic and gory look that the genre is known for.

I saw that you had put a bit of effort into sound effects. Did you write music for the game as well? Do you have an idea where you'll be drawing inspiration from on the soundtrack?
Hi Earworm, thanks for your coments. Before the previous demo (february 2020) we got contacted by a composer starting his career who wanted to make a couple of tracks for the game for exposition. We agreed the terms of the cooperation and they were met by both him and us. However since we barely have any exposition ourselves and I was too hesitant to demand changes (since he was composing for free) I don't think any of us ended very satisfied.
We are planning to release a second, more complete, demo in the following months, and we still haven't decided what to do with the music. I am inclined to use free music, since we are not willing to invest money in the project. We may contact him again or take other offers from beginners but I am not very confortable with accepting collaborations for exposure because, again, we don't have much of that either. We can record the game with the music or even send a working version of the demo to add to your portfolio, but not much more.



Since last month I have been focusing on making the gameplay itself more fun and polishing the enemy behaviour.
  • I removed the "fine" enemy tier and added the defensive "stern" and the ofensive "raging" tiers.
  • Now enemies spawn in groups of two different tiers. This adds some variety and encourages target priority.
  • Enemies of different tiers have different movement and animation speeds. This way the small "swarm" enemies seem more active, the ofensive "raging" enemies more threatening and the stern enemies are slower and seem heavier.
  • Now enemies roam around a bit instead of staying still in idle animation and looking north until they detect a player creature.
  • Now enemies drop life and mana orbs when packlings are low on those resources. Not sure how to use this exactly since I want players to use skills for healing, but also want to give options to survive when the player still doesnt have a support creature.




I have been also working on the bonus stats and how are them calculated and represented.




Not only for the equipment, but also the phenotype. The phenotype is the equivalent of the EVs in pokemon or the stats allocated manually in other RPGs. Phenotype can be raised by crafting consumable items to be used on creatures, or by picking up rare enemy drops called boosters.


Logged
buto
Level 0
***



View Profile WWW
« Reply #44 on: September 20, 2020, 12:51:43 PM »

Reading your thread for the first time, I just wanted to say that I really appreciate the detail in some of your posts regarding technical approaches and difficulties.
I'm also impressed by some of the polish that seems to be in your game, already.

You raised some interesting points, too, like changing the rendering pipeline midway. Now that some time has passed since then, do you think it payed off? Is it easier to implement the desired effects now? Or do you think you should have gone for a slightly simpler look or less shiny effects but saved some time for other things?

Thanks and kind regards,
Sebastian
Logged

arturoblanco
Level 0
***


View Profile WWW
« Reply #45 on: September 20, 2020, 02:15:13 PM »

Reading your thread for the first time, I just wanted to say that I really appreciate the detail in some of your posts regarding technical approaches and difficulties.
I'm also impressed by some of the polish that seems to be in your game, already.

You raised some interesting points, too, like changing the rendering pipeline midway. Now that some time has passed since then, do you think it payed off? Is it easier to implement the desired effects now? Or do you think you should have gone for a slightly simpler look or less shiny effects but saved some time for other things?

Thanks and kind regards,
Sebastian

Hello Sebastian, thanks for your feedback. I wouldn't change the pipeline if I were thinking in results alone, to be honest. Same results could be obtained learning hlsl or hiring/collaborating with somebody who did. It was more of a whim to use shadergraph now that I think of it.
Logged
arturoblanco
Level 0
***


View Profile WWW
« Reply #46 on: October 03, 2020, 08:32:50 AM »

This week I have been slacking from starting an in game event system for level design to make tutorials and such, and instead I remade the "ground cracks" vfx that was quite poor.

This is the result




This is a twitter thread explaining a little.
https://twitter.com/feitizogames/status/1311382790819151874

Also, I forgot to post about the artist finishing the 3rd form of the 3rd initial packling, completing the initial rooster  Tears of Joy



Logged
arturoblanco
Level 0
***


View Profile WWW
« Reply #47 on: January 09, 2022, 10:31:48 AM »

So I am unemployed again! Time to continue working on personal projects.

A couple of years ago I had to adapt the game to the LightWeigth Render Pipeline and it was a headache but no worries, I gained the experience to do it all over again the past couple of weeks, when I updated unity and had to migrate all shaders to the Universal Render Pipeline.

But now I am done with that and I can go back to add new features! In this case, I wanted to add some progressión/story. The first step is the dialogue system to create NPCs the player can talk to. I am doing something rather simple, without dialogue choices. The challenge will be to unify this with other actions such as beating an enemy, capturing another packing, picking up certain items so those actions generate changes in the scene an move the "plot" forward.

My goal is to make an initial mission where the player has to learn to use skills, heal, capture creatures, etc to serve as an integrated tutorial.



Logged
arturoblanco
Level 0
***


View Profile WWW
« Reply #48 on: February 23, 2022, 04:43:51 PM »

I have been continuing with that goal of making an initial mission where the player has to learn to use skills, heal, capture creatures, etc to serve as an integrated tutorial.





My first approach was to create a system that could read from scriptable objects and set up goals for the player. I created a quite a lot of classes, from "Milestones" which represented each step in the mission to the goals and results of achieving milestones. Those were abstract classes and then there were the specific classes for each type of goal: For example for the "eliminate X amount of enemies" goal, activating the goal would spawn the creatures to destroy and subscribe to the "creature death" event to keep track.

Code:
public abstract class MilestoneGoalBase
{
    protected MilestoneGoalData myGoalData;

    protected bool done = false;

    public delegate void GoalAchieved();
    public event GoalAchieved GoalAchievedEvent;

    public static MilestoneGoalBase CreateMilestone(MilestoneGoalData data)
    {
        MilestoneGoalBase milestoneGoal = null;

        foreach (Type type in
            Assembly.GetAssembly(typeof(MilestoneGoalBase)).GetTypes()
            .Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(MilestoneGoalBase))))
        {
            MilestoneGoalBase mgb = (MilestoneGoalBase)Activator.CreateInstance(type);
            if (mgb.GetGoalType() == data.goalType)
            {
                milestoneGoal = mgb;
                milestoneGoal.ActivateGoal(data);
                break;
            }
        }

        if (milestoneGoal == null)
            Debug.LogError("Attempting to create an Objective Milestone Goal with a MilestoneGoalType with no matching MilestoneGoal implementation.");

        return milestoneGoal;
    }

    protected void CallGoalAchievedEvent()
    {
        GoalAchievedEvent.Invoke();
    }

    public abstract MilestoneGoalType GetGoalType();
    public void ActivateGoal(MilestoneGoalData data)
    {
        myGoalData = data;
        ActivateGoal();
    }

    protected abstract void ActivateGoal();

    public abstract bool CheckGoal();
}

public class MilestoneGoalDefeatEnemy : MilestoneGoalBase
{
    private GameObject enemyToDefeat;

    public override MilestoneGoalType GetGoalType()
    {
        return MilestoneGoalType.DEFEAT_ENEMY;
    }

    protected override void ActivateGoal()
    {
        GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
        enemyToDefeat = Array.Find(enemies, (x) => x.name == myGoalData.goalTargetName);

        CreatureHitPoints enemyHitPoints = enemyToDefeat.GetComponent<CreatureHitPoints>();
        enemyHitPoints.KnockOutEvent += EnemyHitPoints_KnockOutEvent;
    }

    ~MilestoneGoalDefeatEnemy()
    {
        if (enemyToDefeat != null)
        {
            CreatureHitPoints enemyHitPoints = enemyToDefeat.GetComponent<CreatureHitPoints>();
            if (enemyHitPoints != null)
                enemyHitPoints.KnockOutEvent -= EnemyHitPoints_KnockOutEvent;
        }
    }

    private void EnemyHitPoints_KnockOutEvent()
    {
        done = true;
        CallGoalAchievedEvent();
    }

    public override bool CheckGoal()
    {
        return done;
    }
}

I lost my patience after a week or so and probably I would come back to complete that in the future, but for now I am taking a more blunt aproach and there is a script called "tutorial.cs" and it does what specifically must be done for the tutorial, no flexibility or reuse potential.

Code:
public class TutorialManager : MonoBehaviour
{
    [Header("StartingPacklings")]
    [SerializeField] private List<Specimen> startingPacklings;

    [Header("Conversations")]
    [SerializeField] private NPC GuideNPC;
    [SerializeField] private Dialogue.Dialogue defeatEnemiesDialogue;
    [SerializeField] private Dialogue.Dialogue healAllyDialogue;

    [Header("Enemies to defeat")]
    [SerializeField] private Specimen enemyToDefeatSpecimen;
    [SerializeField] private int enemyQuantity;
    private List<CreatureHitPoints> enemiesToDefeat = new List<CreatureHitPoints>();
    [SerializeField] Vector3 enemiesSpawnPosition;
    [SerializeField] private GameObject baseEnemy;

    [Header("Ally to heal")]
    [SerializeField] private Specimen allyToHeal;

    private void OnEnable()
    {
        CreatureStorage.activePack.Clear();

        foreach (GameObject creature in GameManager.playerCreatures)
            Destroy(creature);

        GameManager.playerCreatures.Clear();

        foreach (Specimen packling in startingPacklings)
            CreatureStorage.AddSpecimen(packling, Vector3.zero);

        GameManager.SelectPlayer(GameManager.playerCreatures[0]);

        GuideNPC.SetDialogue(defeatEnemiesDialogue);
        DialogManager.Ref.DialogEndedEvent += OnGuideDefeatEnemiesDialogueEnded;
    }

    private void OnGuideDefeatEnemiesDialogueEnded()
    {
        SpawnEnemies();
        DialogManager.Ref.DialogEndedEvent -= OnGuideDefeatEnemiesDialogueEnded;
    }

    public void SpawnEnemies()
    {
        enemiesToDefeat.Clear();

        for (int i = 0; i < enemyQuantity; i++)
        {
            EnemyTier tier = EnemyTier.FAINT;
            GameObject newEnemy = CreatureFactory.CreateCreature(enemyToDefeatSpecimen, baseEnemy, enemiesSpawnPosition, i, tier);

            CreatureHitPoints newEnemyHitPoints = newEnemy.GetComponent<CreatureHitPoints>();
            enemiesToDefeat.Add(newEnemyHitPoints);

            newEnemyHitPoints.KnockOutEvent += OnEnemyKnockOut;
        }
    }

    private void OnEnemyKnockOut()
    {
        bool allDead = true;

        print(allDead);
        foreach (CreatureHitPoints creatureHitPoints in enemiesToDefeat)
        {
            print("BBB");
            if (creatureHitPoints != null && creatureHitPoints.currentHP >= 0)
            {
                print("AAAA");
                allDead = false;
                break;
            }
        }

        if (allDead)
        {
            GuideNPC.SetDialogue(healAllyDialogue);
            DialogManager.Ref.DialogEndedEvent += OnGuideHealPackMateDialogueEnded;
        }
    }

    private void OnGuideHealPackMateDialogueEnded()
    {
        SpawnAllyToHeal();
        DialogManager.Ref.DialogEndedEvent -= OnGuideHealPackMateDialogueEnded;
    }

    public void SpawnAllyToHeal()
    {
        CreatureStorage.AddSpecimen(allyToHeal, GuideNPC.transform.position + new Vector3(10f,0f,10f) );

        CreatureHitPoints allyHitPoints = GameManager.playerCreatures[GameManager.playerCreatures.Count - 1].GetComponent<CreatureHitPoints>();

        allyHitPoints.consciousnessRecoveryRate = 0f;

        allyHitPoints.ConsciousnessChangedEvent += AllyHitPoints_ConsciousnessChangedEvent;

        IEnumerator KillPackling()
        {
            yield return new WaitForSeconds(0.2f);
            allyHitPoints.Die();
        }

        StartCoroutine(KillPackling());
    }

    private void AllyHitPoints_ConsciousnessChangedEvent(float health, float maxHealth)
    {
        CreatureHitPoints allyHitPoints = GameManager.playerCreatures[GameManager.playerCreatures.Count - 1].GetComponent<CreatureHitPoints>();

        if (allyHitPoints.consciousness > 0)
        {
            allyHitPoints.RecoverConsciousness(100000);

            allyHitPoints.consciousnessRecoveryRate = 0.1f;
        }
    }
}

I still can salvage code from this if I decide to do the more powerful mission system, but right now I lack the motivation to spend time and energy on that.

This is still not that simple, the hardest part of making a tutorial, IMO, is to avoid messing up your perfectly functional regular situation code. Once you start having "if (tutorial) " in your mechanics scripts, you are in for a debugging nightmare.

I only made a couple of functions "public", but I am not even confortable with that. What is more, I used a very sketchy solution to getting the player creatures I wanted on the field and working.

Usually the GameManager will spawn the player creatures after getting the information it needs from the CreatureStorage script, that manages loading and saving the creatures to the save game. But I want to create one of those typical RPG scenes where the player controls characters different from the ones saved in their game. So the script has to somehow change the creatures. What I am currently doing is accessing data from the GameManager that by all means should be private or at least public get private set. Then I tell the GameManager to spawn other creatures.

Other similar problems in avoiding the payer defeating the enemies or healing the ally before the time comes for them to do that. Right now the player can not do it because those entities are only spawned when they are needed, but that is not pretty. I might create some roadblocks so the enemies can not be reached or something like that.
Logged
Pages: 1 2 [3]
Print
Jump to:  

Theme orange-lt created by panic