Black Friday, Black Friday ... tired. I announce my personal White Monday - for a couple of nights I wrote a small game and post its code for general use,
with a 90% discount . Why do I need this? Well, I see the following advantages - the very open source code for job search (yes, yes, now I am in active search), read in the comments about my jambs, and finally change the status to Habré.
The essence of the game
The idea came suddenly, and until it flew away, decided to write it down and then embody it. Imagine a cold, dark cell in a certain prison. In it sits, it is not known as well as it is not known by whom, the chained wizard, who every night torments every evil. With his last strength, he creates a small fireball and breathes a semblance of life into it.
Get to know this is Kalcifer, your avatar in this game.
It is to them you will destroy all the filth, which is creeping in the direction of our chained poor fellow.
')
Referring to the codeExplanation of the code
Let's start with the
GameManager . He is the head of everything, it is in him that the state of the game changes - Initialization-> GameLoop-> Win or Lose. Single and single, for singleton. Since the game is not a network, simple, and without complex transitions, it was decided to use this pattern. Here is the processing of hits on the player (see below
Known problems ), accounting hit points and checking for winnings / losses. There would be a
GodObject , but we have too few classes, so I know not all about everyone. At the initialization stage, a pool of objects is created that reflect the animation of damage to the player and the death of enemies. To track the state of hit points, you can subscribe to
UpdateHpWizardDelegate or
UpdateHpCalciferDelegate . In our case,
GUIManager does
this to display the current hp on the screen.

By this time,
SpawnManager has already compiled a list of enemy spawn points.

a
WaveManager uploaded the order of the waves of the creation of enemies. Waves can be configured in two ways: register in the game code or download from the Json file. To edit this Json a custom editor is written:
GameDataEditor
You can register the exact number of spawn or indicate that you can create on any free one.
Creation of waves made with the help of sly Korutina:
Spawnwavesprivate IEnumerator SpawnWaves() { yield return new WaitForSeconds(firstWaveDelay); while (currentWave < waves.Count) { if (!CheckFinishedCurrentWave()) { var step = waves[currentWave].GetCurrentWaveStep(); if (step != null) { yield return new WaitForSeconds(step.delay); var spawners = step.spawners; if (spawners != null) { foreach (var spawn in spawners) { SpawnPoint spawnPoint; if (spawn.index == Consts.INDEX_RANDOM_SPAWN_POINT) { spawnPoint = SpawnManager.S_Instance.GetRandomEmptySpawnPoint(); } else { spawnPoint = SpawnManager.S_Instance.GetSpawnPointByIndex(spawn.index); } if (spawnPoint != null) { SpawnManager.S_Instance.SpawnEnemy(spawnPoint, spawn.name); } else { throw new Exception("Not empty spawn point"); } } } Debug.Log(step.text); } waves[currentWave].currentStep++; } else { SelectNextWave(); } } }
First,
firstWaveDelay seconds before the start of the first wave 1. After that, in a cycle, all the waves are driven in turn, inserting the desired delay
step.delay between the wave steps. Why in korutina and not for example in Update? Yes, in fact, it is possible this way and that, just here it is more vivid, you can see where the delay (
yield return new WaitForSeconds ) and you don’t have to keep unnecessary cycles and checks.
Let's see what SpawnPoint is all
about . This is MonoBehavior with 2 components:
SpawnPoint and
CircleCollider2D . In the first, with the help of the second, it is determined whether the spawn is busy with some sort of enemy.
OnDrawGizmos displays
spawn locations in the Unity editor.
OnDrawGizmos void OnDrawGizmos() { if (m_IsDirty) { Gizmos.color = Color.red; } else { Gizmos.color = Color.green; } Gizmos.DrawSphere(transform.position, 0.3f); }

All enemies come from the base class
BasicEnemy in which there are several virtual methods:
- ContactWizard () , to interact with the wizard
- ContactCalcifer () , for interacting with Calcifer
- ShowDeathAnimation () , to show the death animation
They can be redefined in descendants, for a different reaction to these events. For example,
PoisonEnemy hurts when Calcifer is touched, unlike other enemies.
PoisonEnemy.ContactCalcifer public override void ContactCalcifer() { GameManager.S_Instance.DamageCalcifer(damage); base.ContactCalcifer(); }
By the way, we do not delete enemies and many other objects (many and often created on the scene) using
Destroy (this) , but send it back to the
object pool -
ObjectPool.Recycle (this) . So we save a lot on the creation of objects, which is known to be quite a costly business.
For example, animations end with a call to
SelfDestroy () , which returns the animation object back to the pool.

The enemies are moving with the help of
the pathos force of the BasicEnemyMoving component. In it, we are interested in two methods:
OnEnable () and
Move () .
OnEnable () is called after pulling the enemy from the pool and is needed to turn the enemy (if necessary) in the direction of the target.
OnEnable public void OnEnable() { if (obj!=null&&obj.NeedRotateForDirection) { Vector3 moveDirection = transform.position - GameManager.S_Instance.wizardTransform.position; if (moveDirection != Vector3.zero) { float angle = Mathf.Atan2(moveDirection.y, moveDirection.x) * Mathf.Rad2Deg-90; transform.rotation = Quaternion.AngleAxis(angle, Vector3.forward); } } }
Move () is a virtual method that moves the enemy to the goal. It can be redefined in descendants and make a special movement (with jerks, sinusoidal, etc.)
Move public virtual void Move() { transform.position = Vector3.MoveTowards(transform.position, SpawnManager.S_Instance.TEMP_GOAL.position, speed * Time.deltaTime); }
On this as if everything.
Known shoals
- Singletons
- No Wizard and Calcifer classes. All logic with their interaction lies in GameManager
- Magic numbers with rotation angles of sprites. Yes, I understand that it would be necessary to bring all the sprites to one corner and already from him to dance. But not enough time.
- There is no progress record
- Curve ad prefabs enemies in SpawnManager
Todo
- Change of day / night. Happy rest, scatter skills, etc. At night you burn vrazhin.
- New types of enemies: shooting, hurting Calcifer, leaving slime / cobwebs, teleporting, breeding
- Calcifer pumping: stats, tower defense, skills
- Several endings. There are a couple of ideas about several scenarios, in cases of loss at different stages / different pumping
- Lighting. The walls of the chamber are illuminated by Calcifer, as it grows, more and more.
- Cut scene.
Lights animation taken from
powstudios.comFor the magician's sprite, a special thank you to cute
Kori Tyan