You are absolutely right! As for now these are still placeholders, but the standard collider boxes are a bit too big and need to be tuned manually.
It looks cool
Version 0.07
Enemies now chase players. First they were moving very erratingly, but I found out this had to do with their turn speed. When I decreased it to 10%, they started behaving a lot more natural. Sometimes the animations get stuck, but these are placeholders anyway: this is mainly about the enemies going after the player when he gets close, and stopping the chase when the player is too far away.
Also added the start of the story line as audio.
Version 0.08
Direct WASD mode and indirect mouse mode both implemented now. This was harder than I thought, as the terrain and objects use different ways of addressing the destination (the terrain uses the NavMeshAgent’s Vector 3 position while objects like enemies use the ThirdPersonCharacter’s transforms), and both methods change variables the other method uses (like the ThirdPersonCharacter’s movement speed). So each time the player switches between WASD and mouse controls, variables need to be reset in exactly the right way.
Version 0.09
New player model. There were issues with the enemy health bars suddenly being rendered over the player. The cause turned out to be the Ztest being turned to Off instead to LEqual in the health bar asset from the Unity Store. For this I had to edit the actual shader rendering the health bar, which was completely new terrain for me.
Hi Loek,
Nice work above, looking really good.
Just as a heads-up, if you don’t make your video URLs a link, they will actually embed into your posts, just literally copy/paste the URL in and it’s all done
Hope this helps.
See also;
The forum will support a file size of up to 10mb, if your video is larger than this you could consider uploading to YouTube and then embedding the video into your post.
See also;
- Forum User Guides : How to embed a YouTube video within your post
Ah, thank you for your kind words and for the tip, Rob! I’ll go over my posts and link the videos that way.
You’re very welcome
Version 0.094
Player now has colors. New projectile that detects hits. Player now has full friction. Projectile now works with OnTriggerEnter instead of OnCollisionEnter.
The hard part was the fact that my health bar architecture is vastly different from Ben’s (it’s on the game canvas and not a component of the player), so that the player’s health bar could not use interfaces. Another thing was that the damage is linear, while the mask used for the player health bar is non-linear. On mycurvefit.com I was able to figure out a quadratic regression formula taking care of this conversion (float convertToMask = 4.524476f + 1.999277f * currentHealthPoints + -0.01071096f * currentHealthPoints * currentHealthPoints, I don’t think I would have come up with that myself)!
So much time spent. So little done. So much learned.
I finally got enemy health bars working individually using masks. This involves instantiating new shaders and materials on the fly whenever damage is taken. A pretty badly documented feature, so in the end I spent 9 hours trying every possible combination until I hit bull’s eye.
Note that he big difference with using a simple Fill Amount is the fact that the health bar is masked diagonally instead of vertically. This fits the slant design.
The magic happens here:
public void TakeDamage(float damage)
{
currentHealthPoints = Mathf.Clamp(currentHealthPoints - damage, minHealthPoints, maxHealthPoints);
float invertedPoints = maxHealthPoints - currentHealthPoints;
float convertToMask = 4.524476f + 1.999277f * currentHealthPoints + -0.01071096f * currentHealthPoints * currentHealthPoints;
Material enemyHealthBarMaterial = new Material(Shader.Find("AlchemistLab/UI/HealthBar"));
enemyHealthBarMaterial.SetTexture("_MaskTex", enemyHealthBarTexture);
enemyHealthBarMaterial.SetFloat("_Value", convertToMask / 100);
enemyHealthBarImage.material = enemyHealthBarMaterial;
print("Enemy has " + currentHealthPoints + " HP left, converted to " + (convertToMask / 100) + " mask");
}
Version 0.097
Enemies now have an attack radius and a chase radius. During chases, they stay vigilant and only stop chasing when the player is very far away, due to the implementation of a new start chase and stop chase radius.
This was a kind of bonus assignment from Ben, and it’s harder than you think, as with three radiuses, the logic becomes a lot more complicated. The below is the result of a lot of trial and error.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
using UnityStandardAssets.Characters.ThirdPerson;
[RequireComponent(typeof(NavMeshAgent))]
[RequireComponent(typeof(ThirdPersonCharacter))]
public class Enemy : MonoBehaviour, IDamageable
{
[SerializeField] float attackRadius = 30f;
[SerializeField] float chaseStartRadius = 5f;
[SerializeField] float chaseStopRadius = 30f;
[SerializeField] float originStopRadius = 1f;
[Range(0f, 100f)] [SerializeField] float maxHealthPoints = 100f;
[Range(0f, 100f)] [SerializeField] float minHealthPoints = 0f;
[Range(0f, 100f)] [SerializeField] float currentHealthPoints = 100f;
AICharacterControl aiCharacterControl = null;
GameObject player = null;
GameObject enemyCanvas;
Material enemyHealthBarMaterial;
Image[] enemyCanvasImages;
Image enemyHealthBarImage;
public Texture enemyHealthBarTexture;
enum enemyAttackStatus { attacking, notattacking }
enum enemyMoveStatus { still, chasing, returning }
enemyAttackStatus myAttackStatus;
enemyMoveStatus myMoveStatus;
Vector3 originalEnemyPosition;
public float healthAsPercentage { get { return currentHealthPoints / maxHealthPoints; } }
private void Start()
{
player = GameObject.FindGameObjectWithTag("Player");
aiCharacterControl = GetComponent<AICharacterControl>();
enemyCanvas = transform.Find("Enemy Canvas").gameObject;
enemyCanvasImages = enemyCanvas.GetComponentsInChildren<Image>();
foreach (Image myImage in enemyCanvasImages)
{
if (myImage.name == "Inside")
{
enemyHealthBarMaterial = myImage.material;
enemyHealthBarImage = myImage;
}
}
originalEnemyPosition = transform.position;
print(originalEnemyPosition);
}
private void Update()
{
float distanceToPlayer = Vector3.Distance(player.transform.position, transform.position);
float distanceToOrigin = Vector3.Distance(originalEnemyPosition, transform.position);
// print("Distance to origin: " + originalEnemyPosition + " / " + transform.position + " / " + distanceToOrigin);
// print("Distance to player: " + distanceToPlayer);
if (distanceToPlayer <= attackRadius)
{
// print(gameObject.name + " attacking player");
myAttackStatus = enemyAttackStatus.attacking;
}
else
{
// print(gameObject.name + " not attacking player");
myAttackStatus = enemyAttackStatus.notattacking;
}
// Start chasing if player is inside chaseStartRadius
if (distanceToPlayer <= chaseStartRadius && (myMoveStatus == enemyMoveStatus.returning || myMoveStatus == enemyMoveStatus.still))
{
myMoveStatus = enemyMoveStatus.chasing;
print(gameObject.name + " starts chasing player " + myMoveStatus);
}
// Keep chasing if player is inside chaseStopRadius
else if (distanceToPlayer <= chaseStopRadius && (myMoveStatus == enemyMoveStatus.chasing || myMoveStatus == enemyMoveStatus.returning))
{
myMoveStatus = enemyMoveStatus.chasing;
print(gameObject.name + " keeps chasing player " + myMoveStatus);
}
// Return if player is outside chaseStopRadius
else if (distanceToPlayer > chaseStopRadius && distanceToOrigin > originStopRadius && myMoveStatus != enemyMoveStatus.still)
{
myMoveStatus = enemyMoveStatus.returning;
print(gameObject.name + " returns to base " + myMoveStatus);
}
// Stay still
else
{
myMoveStatus = enemyMoveStatus.still;
print(gameObject.name + " is back at base " + myMoveStatus);
}
switch (myMoveStatus)
{
case enemyMoveStatus.still:
aiCharacterControl.SetTarget(transform);
break;
case enemyMoveStatus.chasing:
aiCharacterControl.SetTarget(player.transform);
break;
case enemyMoveStatus.returning:
// Return to original position. Complicated, as stored transform at Start is not transform itself, but always a reference to the current enemy's transform.
aiCharacterControl.agent.SetDestination(originalEnemyPosition);
aiCharacterControl.character.Move(aiCharacterControl.agent.desiredVelocity, false, false);
break;
}
}
public void TakeDamage(float damage)
{
currentHealthPoints = Mathf.Clamp(currentHealthPoints - damage, minHealthPoints, maxHealthPoints);
float convertToMask = 4.524476f + 1.999277f * currentHealthPoints + -0.01071096f * currentHealthPoints * currentHealthPoints;
Material enemyHealthBarMaterial = new Material(Shader.Find("AlchemistLab/UI/HealthBar"));
enemyHealthBarMaterial.SetTexture("_MaskTex", enemyHealthBarTexture);
enemyHealthBarMaterial.SetFloat("_Value", convertToMask / 100);
enemyHealthBarImage.material = enemyHealthBarMaterial;
// print("Enemy has " + currentHealthPoints + " HP left, converted to " + (convertToMask / 100) + " mask");
}
void OnDrawGizmos()
{
// Draw attack sphere
Gizmos.color = new Color(0f, 0f, 255f, 0.5f);
Gizmos.DrawWireSphere(transform.position, attackRadius);
// Draw chase start sphere
Gizmos.color = new Color(255f, 0f, 0, 0.5f);
Gizmos.DrawWireSphere(transform.position, chaseStartRadius);
// Draw chase stop sphere
Gizmos.color = new Color(0f, 255f, 0, 0.5f);
Gizmos.DrawWireSphere(transform.position, chaseStopRadius);
}
}
Version 0.099
Enemies now shoot projectiles at player. First projectile has graphics. Projectile damage and speed can now be set. When attacking, the enemy also looks at the player, to avoid sending arrows in completely the wrong direction. Player can die (disappear).
Version 0.100
Sounds are played when arrow is shot, whizzes close by and hits player.
Version 0.101
It turned out I had two cameras (GAIA and ootii) running at the same time. GAIA seems to suffice for now. Set Rotation Dampening from 0.5 to 0, as it constantly messes with the player’s aim on the enemy when the player is clicking on said enemy. Player can now click on the enemy, which then suffers a fixed amount of damage. Enemies can die. Fights are already offering a real challenge.
Version 0.102
Three different types of enemies (Archer, Bruiser and Minion). Enemies attack based on the availability of a weapon in their slot. Background music changes based on combat status (detected by number of attacking enemies).
Amazing work!!!
Version 0.104
Wolf pet implemented that can walk over terrain and has three basic animations (run, stand and dig). It follows the player and heals us when we stand still. I’m slowly starting to understand how the Animator works, so a lot can still be refined in the future.
The player is now also animated when he hits the enemy, and each hit produces a sound.
Version 0.107
Wolf and attack range refinement. Reinstall of EnviroFX and AQUA because of compiler bugs. Everything is clean now and no errors are produced.
Wolf now flees during fights and says Kaikaikai! when he does so. He’s not a hero and hates fighting. Maybe this will change at higher levels. But once the fight is over, he will come to you and heal you, like the loyal pet he is.
New archer model implemented with arrow nocking animations and archer enemy behavior. TODO: Implement enemy melee animations.
Version 0.109
CTS (Complete Terrain Shader) activated.
All three enemy types now have 2 die, 6 weapon swing and 1 fist blow animation and sound. Die sound based on gender. Enemy behavior completely reworked to accommodate for both ranged and melee types. TODO: Types that can do both melee and ranged.
All enemies have varied animations. Enemies and BGM have a completely revamped combatStatus system.
Enemies now share same audio bank and are working correctly. Audio mixer implemented. Note for self: if you implement an audio bank that you instantiate under each enemy’s hierarchy, don’t forget to reset the transform to Vector3.zero, as else the enemy’s vocal chords will hang somewhere in a tree, so that his voice becomes completely inaudible with 3D sound. Only took me five hours to figure out.
Version 0.115
New enemy type with heavy-hitting fire arrows (this is for Ben’s trail assignment).