Another World: In Development

Twitter: https://twitter.com/DragonceptionSt
YouTube: https://www.youtube.com/channel/UCElXgqs1s_z6LD7kTlsOPHg
Website: https://www.dragonception.com

5 Likes

Version 0.04

Full implementation of CTS (Complete Terrain Shader), AQUAS water, Enviro environment effects with full weather system and day and night cycle, ootii camera and new health bar (the latter is not visible yet).

1 Like

Version 0.05

Terrein detail optimization. Enemy health bars always facing the camera.

1 Like

Version 0.06

More interesting terrain. Rotating objects, like this windmill, including timed woosh sounds.

1 Like

in your video, at abour 0:31 seconds in while walking on the side of that rock… seemed like there was something preventing you from moving to the right (closer to the right)… which kept your character sliding off left while walking forwards. Am I right, or just seeing things?

1 Like

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.

1 Like

It looks cool

2 Likes

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.

1 Like

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.

1 Like

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.

1 Like

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 :slight_smile:

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;

Ah, thank you for your kind words and for the tip, Rob! I’ll go over my posts and link the videos that way.

1 Like

You’re very welcome :slight_smile:

1 Like

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).

Privacy & Terms