top of page

University Work - ARPG Digital Prototype

Updated: May 26, 2023

We were tasked with doing a digital version of our paper prototype (previous post).


The purpose of this prototype, or the questions it should answer:

  • Does the damage system work? Is it balanced?

  • Are attacks meaningful?

  • What AOE size is the right one for each attack? And what range?

  • What's the right speed for a hectic gameplay?

  • It’s fun to play?


PREFABS


Let’s start by doing the assets in Photoshop and making them into Prefabs.



You can see in the Prefabs folder in the bottom what is what.




MAKING THE PLAYER MOVE


Now let’s try to make the player move using some path finding.

Using a video recommended by the lecture from Brackeys (2D PATHFINDING - Enemy AI in Unity) as a first option, I installed the A* Pathfinding Project free package version to give it a try.

There’s two options, create your own AI (a bit too much for me), or to use the premade scripts which is the option I went for. Unfortunately A* Pathfinding system seems to be focused on making enemies follow the player, while what I want to do is the player to move while avoiding obstacles. So all the pathfinding is created so it follows an object. So what I had to do is create an Empty object I called “GetOverHere” (in honor to the famous Scorpion’s shoutout from MortalCombat) that the player’s character follows, and everytime the player clicks on the screen the GetOverHere object translates to the clicked position.




void MovetoAIPath () //With AIPath Finding!!

{

if (Input.GetKeyDown(KeyCode.Mouse0))

{

GetComponent<Pathfinding.AIPath>().canMove = true; //Player is allowed to move

getOverHere.transform.position = Camera.main.ScreenToWorldPoint(Input.mousePosition);

}

}


It works, not great at avoiding obstacles but it does the job. The problem is the movement is very sluggish. The game is supposed to be a fast paced game where micromanagement is key. This path system seems designed for slow movement, because as soon as you increase the speed and try to make it react faster, it just doesn’t work. The player gets too much inertia and bounces back and forth when it reaches its destination, and it's also extremely slow when changing direction as it spends too much time turning around.

This may be useful for the enemies but for the player I’m going to have to find a more reactive/fast system.


A Different Approach


I’m going to try to code it manually without any path finding AIs. Just so I can get a feeling for it and test it against the enemies. 2 days of intense programing later:

OMG!! This is soooo difficult and frustrating!!! T_T

OK, after 2 days fighting unity and C# I managed to create more or less what I wanted.


For some reason I can’t manage to make the player move and rotate at the same time properly. When the player is still, it works more or less fine, but as soon as it starts moving, for some strange reason the rotation doesn’t work well. The good thing is that it goes where you click to go, so it works well, but it doesn’t look good, and I’m good with that since this is a prototype.

Then I added the attack function. This one makes the player stop and attack. For some strange reason the attacks work perfectly in terms of the direction, unlike the player’s turning even though the code I use for both is the same. Unfortunately the projectiles don’t continue moving beyond the click point and honestly I have no idea what I’m doing wrong.


Later update:


The lecturer helped me fix the problem with the projectiles not moving beyond the clicked position. So at least I got a decent moving and shooting player.


The code


You can see I have left the code from the A* Pathfinding Project just in case I want to come back to it later on.

It’s far from great, but it does the job, so I think I’m going to focus now on creating some enemies and test the damage that is more important as it’s one of the questions that need to be answered by the prototype.



MANAGING VARIABLES


While I was coding the script for the first enemy, I realised that my variables in terms of HPs, speed, damage, etc were all scattered in the different prefabs, so making changes was quite time consuming. So I decided to put all the variables in the GameManager.


So I did research about Global Variables, and I saw a post that showed how to use Static variables. So I made a list in my GameManager with all my variables as Static. But it didn’t work, and weirdly enough, they didn’t appear in the Inspector. Very annoyed I decided to do it the traditional way, even if it means more coding and attaching stuff.

The good thing is in the process I learned how to separate and put headers to your lists of variables. Here’s mine:


[Header("Player")]

public int player_HP;

public float player_Speed;


[Header("Level 1 Attack")]

public int L1_FixedDmg;

public int L1_RandomDmg;

public float L1_Speed;

public float L1_Cooldown;


[Header("Level 2 Attack")]

public int L2_FixedDmg;

public int L2_RandomDmg;

public float L2_Speed;

public float L2_Cooldown;


[Header("Level 3 Attack")]

public int L3_FixedDmg;

public int L3_RandomDmg;

public float L3_Speed;

public float L3_Cooldown;


[Header("Nosocuteling")]

public int noso_HP;

public float noso_Speed;

[Space(10)]

public int noso_FixedDmg;

public int noso_RandomDmg;

public float noso_Cooldown;


Now, when accessing these variables in the other scripts, because I’m a smartass and there’s nothing that I love more than to minimize the amount of code I write, I decided to assign the value of the variables on the very line I defined them. In a normal case this would look like:


Public float p_speed= 20;


So I thought, let’s do the whole thing, so I went:


Public float p_speed = gmPlayer.GetComponent<GameManager>().player_Speed;


How smart of me… well, it just doesn’t work, and because I was looking everywhere else, it took me nearly an hour to find out that was the actual problem T_T Why is the first one OK but not the second one?? You assign the same value in both cases, it would make sense… whatever!


COLLISIONS


I added my collision script to the enemy, in this case a Nosecuteling, and then it takes the damage from it, but it’s not working (surprise! surprise!), so after searching the web, I found out you have to add the “2D” bits in the collision function. So with them, it looks like this:


public void OnCollisionEnter2D(Collision2D collision)

{

print("collision");

if (collision.gameObject.CompareTag("Attack_L3"))

{

damage = collision.gameObject.GetComponent<L3_Projectile>().L3_TotalDmg;

nosocute_HP = nosocute_HP - damage;

print("Nosocute HP=" + nosocute_HP);

print("Damage=" + damage);


if (nosocute_HP <= 0)

{

Destroy(gameObject, 0);

} } }


But it still doesn’t work. After lots of experimenting and trying, I found out that it doesn’t work if you set up the rigid bodies to Kinetic, instead I have to set it up to Dynamic but freezing the Position and Rotation movements. Well, at least I’ve got something that works, I can attack my enemies.




I’ve just observed that the damage doesn’t work as it should, but it’s past 1am so I’ll do this tomorrow.


It feels like I haven’t made much progress despite spending the whole day doing this, anyway, hopefully tomorrow will be better.


DAMAGE


After lots of going nuts with Unity, I found out why the damage wasn’t working properly. It seems that when you edit a variable from a prefab there’s a blue line that appears on the left hand side of it, and if you want it to take effect you have to right click on it and select “Apply to Prefab”.




Well, at least I’ve got one thing that works as it should be, the attacking.

This is the code for the damage, it’s on the projectile, and the enemy script “picks it up”. I’ve done this way because every projectile could have (or not) Critical Damage independently of the other projectiles.



Level 2 and 1 Attacks and Projectile Rotation


Let’s add the Level 2 and 1 attacks and I’m going to spoil myself to make the Level 1 and 3 attacks to rotate just to make it more cool. I know I shouldn’t spend time on these things, but it’s such a simple thing to add :)


First I created the scripts for the attacks themselves and copy-paste the code from Level 1 attack and changing the Variables plus adding all the components the prefabs should work. I also need to change the Nosocuteling script to receive the damage.


And of course the TurnMFTurn script to make the projectiles turn :D


Time to try these bad boys against some enemies.



The level 2 attack right now is not the “shotgun cone AOE '' I thought of initially, but I like the way it is now and I want to test against different units with different HP levels before I try the shotgun style one.


Awesome! Now I need to add a cooldown for the attacks, but I’m pretty annoyed with the poor movement and rotation of the player, so I’m going to change the script before anything else.


Oh! Wow! The first script I checked worked perfectly, no problems, no bugs, no need to change the other bits of the code… I didn't know this kind of thing could happen while coding in Unity. Anyway, this is the miraculous code:



Vector3 mousePos = Input.mousePosition;

mousePos.z = 0;

Vector3 objectPos = Camera.main.WorldToScreenPoint(transform.position);

mousePos.x = mousePos.x - objectPos.x;

mousePos.y = mousePos.y - objectPos.y;

float angle = Mathf.Atan2(mousePos.y, mousePos.x) * Mathf.Rad2Deg;

transform.rotation = Quaternion.Euler(new Vector3(0, 0, angle -= 90));



I also decided to remove the PlayerShooter script and add all its code into the PlayerControl as the script’s amount of code wasn’t enough to justify a script of it’s own.


The result:



COOLDOWNS


Well, not only did I add the cooldowns, I also added some cooldown UI indicators. Basically, I added the 3 sprites of the attacks slightly edited at the bottom of the Main Camera and put them as childs so they follow the camera. And then I activate or deactivate them with the cooldowns, this way you know when your skill is ready to be used again. It’s a very cheap UI system, but that’s the beauty of prototypes, if it works, it’s fine :)

For some strange reason, being able to create very fast and cheap coding that works provides me with immense pleasure :D


Here’s my cheap code:


//LEVEL 1 ATTACK

if (Time.time > L1_NextShoot)

{

L1_Pro_UI.gameObject.SetActive(true);

if (Input.GetKeyDown(KeyCode.Alpha4))

{

L1_NextShoot = Time.time + L1_Cooldown_;

Instantiate(L1_Projectile, transform.position, transform.rotation);

L1_Pro_UI.gameObject.SetActive(false);

}

}


For the other attacks it’s exactly the same but with the numbers changed.


The first thing I noticed is that the cooldowns were a bit too high, so I reduced them drastically. Anway, this will have to be adjusted further one it’s all working and the gameplay can be tested properly.



ENEMIES


Now it’s time to create some extra enemies, I’m going to add the Cuteling that is the other melee enemy and see if I can make them work with the A* Pathfinding Project scripts to stalk the player.


I don’t know why it’s not working, but basically the enemies just move slightly in the direction of the player, they stop, and that’s it. After some research I still can’t find what’s going wrong, so I think I'm going to forget about pathfinding AI pathfinding. It would have made things much easier, but it’s not important for the purpose of the prototype and I’ve already wasted too many days trying to make this work without success.


It has taken me the whole day, but at least I’ve managed to make it work. Now I have enemies that follow my character and rotate towards it.



They tend to cluster very tightly, this is something that can be fixed by adding Rigidbody components, but that makes them bounce back when they collide with the player’s attacks. I’ve tried all types of setups and options with the Collider and Rigidbody components without success. I’ll have to ask and see if this can be avoided.


The Canonling and the Shooterling now throw their projectiles but they still don’t do damage to the player (next step), but getting there. I’ve also done some adjustments to the enemy’s movement speeds and attack speeds and range.





ENEMIES ATTACK


I’ve managed to make all my enemies damage the player. The ranged enemies damage system is totally legit, but the melee ones, not so much. I just didn’t know how to implement a melee attack or at least not in a non highly consuming time. I thought about doing an extremely short range projectile attack, but I wasn’t convinced, so I tried with collisions, and I managed to make it work, but only the first collision, so if the enemies stay close to the player, the attacks don’t resolve. OK, let’s try with very short range projectiles.


Yess!! Finally! All my enemies are attacking and damaging my player. At the end the short range projectile mechanic for the melee enemies turned out to be a much better option and since I already had the scripts from the range units, it was much easier.


public class Nosocuteling_Attack : MonoBehaviour

{

public GameObject GMNosoAttack;

public Transform nosoPlayerPos;


private float nosoProSpeed_;

public int nosoTotalDmg;


void Start()

{

nosoProSpeed_ = GMNosoAttack.GetComponent<GameManager>().noso_Pro_Speed;

int nosoFixedDmg = GMNosoAttack.GetComponent<GameManager>().noso_FixedDmg;

int nosotRandomDmg = GMNosoAttack.GetComponent<GameManager>().noso_RandomDmg;

Destroy(gameObject, 0.1f);


nosoTotalDmg = nosoFixedDmg + Random.Range(0, nosotRandomDmg + 1);

}


void Update()

{

transform.Translate(Vector2.up * nosoProSpeed_ * Time.deltaTime);

}

}



I’ve changed the Level 2 attack to the shotgun style AOE. It now requires slightly better positioning to make it work well, but if you do, it can have a bigger AOE.

This is the code that makes it grow:


float A = 3 * Time.deltaTime;

transform.localScale = new Vector3(transform.localScale.x + A, transform.localScale.y + A, transform.localScale.z + A);



GAME MANAGER


The game manager is now in its final version with all the variables and values.





TESTING


Finally I get to do some actual testing. After playing the scene a few times I made some corrections:


  • Canonling Damage increased from 10+1D10 to 20+1D10

  • Canonling Attack Cooldown increased from 3 to 4 seconds

  • Canonling Projectile Speed increased from 30 to 40


  • Shooterling Damage increased from 6+1D6 to 10+1D5

  • Nosecuteling Damage increased from 4+1D6 to 6+1D4

  • Nosecuteling Moving Speed decreased from 10 to 9


Encounter with bigger enemies


This was harder, but I managed to beat it after a few attempts. Very fast and hectic, but also fun.


Cutelings Massive Attack



This one was pretty easy, you just make them follow you a bit so they group up and then you blast them away with a Level 1 attack. This in enclosed encounters may prove more tricky. Anyway, the Cutelings' purpose is to die easily and by the hundreds for the player’s pleasure and enjoyment.


Flanked by 2 groups


This was considerably more difficult. I died several times before I managed to beat it, but it was quite intense. I loved it.




CONCLUSION


Obviously this is all done in an empty space with no walls or limitations as I can’t make the path finding work in 2D, and it’s way above my programming skills to do one from the ground up. But for the purpose of this prototype, it has done what it was meant to do, it has answered all the questions it was meant to answer:


  • Does the damage system work? Is it balanced?

Yes.


  • Are attacks meaningful?

Yes.


  • What AOE size is the right one for each attack? And what range?

Done.


  • What's the right speed for a hectic gameplay?

Done.


  • It’s fun to play?

Hell yeah!



I don’t think I have ever written so many lines of code in my life in one project. I have to admit my coding standards have been a bit lacking, but I was just interested in making it work rather than making it effective or efficient. Also, I started with my own Variables Naming Convention that I forgot about half way through it. Anyway, here’s the balance:



The negatives


  • I didn’t manage to get the bloody AI PathFinding thing to work. This would have saved me a lot of time and allowed me to create some improvised levels to test it in more realistic conditions. Instead I just wasted a lot of time trying to make it work for nothing.

  • I didn’t manage to implement a better system of global variables, and instead abused the GetComponent function. But since it is a prototype and it doesn't need to be efficient or polished, I’m OK with it.

  • I haven’t managed to make the enemies stop piling on top of each other, eventually looking like one single enemy. But it was that, or make them bounce back every time they collided with an attack. The exception are the Cutelings, because they are always killed by the first attack, so I could keep the physics on.

  • I haven’t had time to implement the player’s defensive skills, but this prototype was more about the damage and the attacks.


The positives


  • I’ve learnt how to format variables and create regions in Visual Studio.

  • I’ve learnt how to do Cooldowns.

  • Even though I copy pasted the code for the script part that orientates my enemies and player, and I still don’t fully understand it, I’ve learned a thing or two about how to move objects towards other objects and mouse clicks.


This has been quite a wild ride of very frustrating times but also gratifying ones, unfortunately I think the frustrating ones outnumber the former, but I guess that is how you learn, and the more you learn you’ll get less and less frustrating times. Anyway, next time I code something, I’ll try to be a bit more consistent and smarter with my variables. I feel like I've developed a bit of “code thinking” these days. Spending so much time coding non stop day after day, kind got me used to think in C#... I think.


1 Comment


Unknown member
May 02, 2023

Cool 😃

Like
bottom of page