top of page

THE DUALITY RUIN -GOOD & EVIL-

Engine: Unity     
Platform: Windows     
Genre: Dungeon Crawler

System requirements:

  • OS: Windows 7 or later

  • CPU:Intel® Core™ i7-4790 or higher level

  • RAM:8 GB or above

  • Display: NVIDIA GeForce GTX 750 Ti or higher level

  • Storage:  At least 80GB

Dungeon Crawler Jam 2023 link: https://itch.io/jam/dcjam2023/rate/2006860

ckNGr6.png
Source Assets Unavailable

Game control

W: Forward
S: Back
A: Left
D: Right
Q: Turn Left
E: Turn Right
Left Click: Attack
Right Click: Change Weapon
P: Pause Menu
F12: Screenshot

Game Story

Your government discovered a ruin called The Duality Ruin. As a soldier, you have been assigned by the government to explore this ruin. 

Your duty includes searching for treasure and delivering it to the government, helping people in trouble and retrieving the relics of the dead.

However, you can also choose to do good or evil during the course of your adventure. This is your right.

In addition, the ruin include good and evil power. Your behavioral tendencies will affect your adventures. At the end of the ruin, you will discover a strange things. It can affect the whole world according your good and evil.

Game Stage

1: Level-1
2: Level-2
3: Level-3

4: Final Boss

Game Play

  • Value description:

    • HP: If you were attacked, you will lost HP. If it become 0, you will die.

    • Attack: Your weapon's power.

    • Armor: Your armor's power. Armor can decrease your damage.

    • Hunger: As time progresses you will get hungry, therefore you need food. If it become 0, your HP will continuously lost.

    • Fatigue: You need your physical strength to move or melee.  If it become 0, you cannot move and melee attack. Wait 5 seconds can recover 1 fatigue point.

    • Money: You need use your money to buy items.

    • Bullet: You need use bullet to shoot the gun.

    • Spoils: Total value of treasures. You can hand over to the government or expropriate them.

    • Relic: Relics of the dead you found. You can hand over to the government or expropriate them.

  • Items:

    • Medicine: +20HP

    • Hi-Medicine: +50HP

    • Food: +20Hunger

    • Sports Drink: +30Fatigue

    • Super Meal: +50HP +50Hunger +50Fatigue

    • Bullet Pack: +10Bullet

  • Combat mechanic:

    • Overall

      • You will meet different enemy in the dungeon.

      • They try to kill you.

      • you need to use your weapon to protect myself.

    • Enemy

      • They can move by four direction randomly.

      • If they are face to you, they will rush to you and start to attack. Please avoid this situation.

      • When you go to higher level areas, you will encounter stronger enemies.

    • Weapon

      • Melee: Default weapon, attack enemies at close range with a larger damage. You need to use your fatigue point to do melee attack.

      • Gun: Attack enemies at long range with a smaller damage. Bullet is required to shoot

  • Karma:

    • Overall:

      • Doing more good will make you tend to be good. And doing a lot of bad things makes you tend to be evil.

      • Your karma will not show in the game directly.

    • Below behavior will make you tend to good(Increase karma point):

      • Help NPCs in the dungeon.

      • Turn in spoils to government.

      • Turn in relics to government.

      • Devote your money to church.

    • Below behavior will make you tend to evil(Deduct karma point):

      • Kill NPCs in the dungeon.

      • Expropriate the spoils by yourself

      • Expropriate the relics by yourself

  • Karma Status:

    • Overall:

      • The game have 5 karma level according your karma point. They will affect your game and the ending. Your karma points starts at 0.

      • You can see your status in the church's mirror.

    • Extremely Good - Karma point >=50

      • All item's price are 50% off.

      • Damage to enemies with evil attribute(Black enemy) will be doubled.

      • Kill the enemies with evil attribute(Black enemy) can +1 Armor Point.

    • Good - Karma point >=20

      • All item's price are 25% off.

    • Normal - Karma point >=-20

      • All item's price are normal.

      • The whole game states are normal.

      • Your initial state.

    • Evil - Karma point >=-20

      • All item's price are become 125%.

    • Extremely Evil - Karma point <-50

      • You cannot buy anything in the store.

      • Damage to enemies with good attribute(White enemy) will be doubled.

      • Kill the enemies with good attribute(White enemy) can +1 Attack Point.

  • Dungeon's object:

    • Overall:

      • You  can find different things in the dungeon. Include red treasure box, blue treasure box, NPC and corpse.

    • Red treasure box

      • Contains spoils worth from 1 to 3000.

      • Appears in a random position.

    • NPC

      • They are in trouble that need you help.

      • Increase your karma point if you help them.

      • You also can kill them to make them become a corpse. That will make you lost karma point.

      • Appears in a random position.

    • Corpse

      • Get relics from these corpse.

      • Appears in a random position.

    • Blue treasure box

      • Contains a key go to next level.

      • Appears in a fixed position.

Essence Code(C#)

Player Input

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(PlayerController))]
public class PlayerInput : MonoBehaviour
{
    // Start is called before the first frame update
    public KeyCode forward = KeyCode.W;
    public KeyCode back = KeyCode.S;
    public KeyCode left = KeyCode.A;
    public KeyCode right = KeyCode.D;
    public KeyCode turnLeft = KeyCode.Q;
    public KeyCode turnRight = KeyCode.E;

    PlayerController controller;

    private void Awake()
    {
        controller = GetComponent<PlayerController>();
    }


    // Update is called once per frame
    void Update()
    {
        if ( Pause.pauseDisable == false)
        {
            if (PlayerProfile.PlayerData.fatigue > 0 && Input.GetKeyUp(forward)) { PlayerProfile.PlayerData.fatigue -= 1; controller.MoveForward(); }
            if (PlayerProfile.PlayerData.fatigue > 0 && Input.GetKeyUp(back)) { PlayerProfile.PlayerData.fatigue -= 1; controller.MoveBackward(); }
            if (PlayerProfile.PlayerData.fatigue > 0 && Input.GetKeyUp(left)) { PlayerProfile.PlayerData.fatigue -= 1; controller.MoveLeft(); }
            if (PlayerProfile.PlayerData.fatigue > 0 && Input.GetKeyUp(right)) { PlayerProfile.PlayerData.fatigue -= 1; controller.MoveRight(); }
            if (Input.GetKeyUp(turnLeft)) {controller.RotateLeft(); }
            if (Input.GetKeyUp(turnRight)) {controller.RotateRight(); }
        }
        
    }
}

Player Controller

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public bool smoothTransition = false;
    public float transitionSpeed = 10f;
    public float transitionRotationSpeed = 500f;

    int HitNum;

    public GameObject wayback;

    Vector3 targetGridPos;
    Vector3 prevTargetGridPos;
    Vector3 targetRotation;

    // Start is called before the first frame update
    void Start()
    {
        if (PlayerProfile.PlayerData.Wayback==true)
        {
            targetGridPos = Vector3Int.RoundToInt(wayback.transform.position);
            transform.position = wayback.transform.position;
            targetRotation = Vector3.up * 180f;
            
        }
        else
        {
            targetGridPos = Vector3Int.RoundToInt(transform.position);
        }
        
        Debug.Log(targetGridPos.x + "," + targetGridPos.z);
        targetGridPos.y = 0.5f;

    }

    private void FixedUpdate()
    {
        MovePlayer();
    }

    void MovePlayer()
    {
        //if (Mathf.RoundToInt(targetGridPos.x)%2==0 || Mathf.RoundToInt(targetGridPos.z) % 2 == 0)
        if ((CollManger.CollData[Mathf.RoundToInt(targetGridPos.x), Mathf.RoundToInt(targetGridPos.z)] != 1) && (CollManger.CollData[Mathf.RoundToInt(targetGridPos.x), Mathf.RoundToInt(targetGridPos.z)] != 3))
        {

            CollManger.CollData[Mathf.RoundToInt(prevTargetGridPos.x), Mathf.RoundToInt(prevTargetGridPos.z)] = 0;
            prevTargetGridPos = targetGridPos;
            Vector3 targetPosition = targetGridPos;

            if (targetRotation.y > 270f && targetRotation.y < 361f) { targetRotation.y = 0f; }
            if (targetRotation.y < 0f) { targetRotation.y = 270f; }

            if (!smoothTransition)
            {
                transform.position = targetPosition;
                transform.rotation = Quaternion.Euler(targetRotation);
            }
            else
            {
                transform.position = Vector3.MoveTowards(transform.position, targetPosition, Time.deltaTime * transitionSpeed);
                transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(targetRotation), Time.deltaTime * transitionRotationSpeed);
            }

            CollManger.CollData[Mathf.RoundToInt(targetGridPos.x), Mathf.RoundToInt(targetGridPos.z)] = 2;

        }
        else
        {
            targetGridPos = prevTargetGridPos;
        }
    }

    public void RotateLeft() { if (AtRest) targetRotation -= Vector3.up * 90f; }
    public void RotateRight() { if (AtRest) targetRotation += Vector3.up * 90f; }
    public void MoveForward() { if (AtRest) targetGridPos += transform.forward; }
    public void MoveBackward() { if (AtRest) targetGridPos -= transform.forward; }
    public void MoveLeft() { if (AtRest) targetGridPos -= transform.right; }
    public void MoveRight() { if (AtRest) targetGridPos += transform.right; }

    bool AtRest
    {
        get
        {
            if ((Vector3.Distance(transform.position, targetGridPos) < 0.05f) && (Vector3.Distance(transform.eulerAngles, targetRotation) < 0.05f))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

    }

    // Update is called once per frame
    void Update()
    {
        switch (HitNum)
        {
            case 1:
                MoveBackward();
                HitNum = 0;
                break;
            case 2:
                MoveForward();
                HitNum = 0;
                break;
            case 3:
                MoveRight();
                HitNum = 0;
                break;
            case 4:
                MoveLeft();
                HitNum = 0;
                break;
            default:
                break;
        }
    }

    void OnTriggerEnter(Collider other)
    {
        string side;

        Vector3 direction = other.gameObject.transform.position - transform.position;
        float angle = Vector3.Angle(transform.forward, direction);

        if (Vector3.Angle(transform.right, other.gameObject.transform.position - transform.position) > 90f) side = "left"; else side = "right";

        //Debug.Log("Angle: " + angle + " " + side);

        if (other.tag == "hole")
        {
            targetGridPos.y = -10;
            
        }

        if (other.tag == "combat")
        {
            if (angle<=45)
            {
                HitNum = 1;
            }else if (angle <= 135)
            {
                if (side == "left")
                {
                    HitNum = 3;
                }
                else
                {
                    HitNum = 4;
                }
            }
            else
            {
                HitNum = 2;
            }

        }


    }

   

}
 

Collider Set

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollSet : MonoBehaviour
{
    public int objectNum;

    // Start is called before the first frame update
    void Start()
    {
        CollManger.CollData[Mathf.RoundToInt(gameObject.transform.position.x), Mathf.RoundToInt(gameObject.transform.position.z)] = objectNum;
        //Debug.Log(CollManger.CollData[Mathf.RoundToInt(gameObject.transform.position.x), Mathf.RoundToInt(gameObject.transform.position.z)]);

    }

    // Update is called once per frame
    void Update()
    {
        
    }

}

Collider Manger

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CollManger : MonoBehaviour
{
    public int mapSize;
    public static int[,] CollData;

    private void Awake()
    {
        CollData = new int[mapSize, mapSize];

        for (int i = 0; i < mapSize; i++)
        {
            for (int j = 0; j < mapSize; j++)
            {
                CollData[i, j] = 0;
            }
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {

    }
}

bottom of page