top of page

Children are Going Down Stairs -Royale Era-

Engine: Unity+Photon PUN
Platform: Windows(Will add WebGL version soon)
Genre: FPS Action game
This game require network.
This game is not support Mobile Device.

GDWC 2022 link: https://thegdwc.com/games/a112ae16-a7b6-41f9-98e2-6e19275dfd9c

Game control

W A S D: Move character
Shift: Run
Space bar: Jump
R: Recover bullet by use magazine
Mouse left button: Shoot the gun

Background

The game calls “Children are Going Down Stairs -Royale Era-”. It refers to a game which called “NS-Shaft” created by Akihiko Kusanagi(草薙昭彥) and the title is reference from 小朋友落樓梯 which is the common name of NS-Shaft in Hong Kong. The game was hot in past years. Also, this is many people's childhood memory. Therefore, I reshape the game into a 3D multiplayer Action FPS game mix original NS-Shaft game play with a new platform, objects and rules after these years, to bring player evolved “NS-Shaft” game play experience. "-Royale Era-" is mean that the game is NS-Shaft crossover the Battle Royale.

In the game, player need to try to get the bottom and then kill other player by use gun. And player need to avoid other player's attack.

Game Story

Our Earth have many different ruins. And the village also have a ruin. It have a legend: If someone can reach the ruin's bottom, he can get all treasure and control the Earth. Then children in that village also know this legend. Then they go to explore this ruin in order to try to get treasure and control the Earth. However, the ruin has different dangerous that can kill children. Most important thing is each children has their own interests that make them to do fratricide in the ruin.

Game Play

  • Player can join a room or create a room. Maximum is 20 players since the Photon Network free user limit.

  • Player has 100HP If it is damaged, it will be deducted.

  • Player can recover HP by step the board.

  • If player's hp become 0 or fall to bottom or touch the top, player will die. After player die 3 times, player will be kicked out from the game room.

  • The gun have 6 bullets and 3 magazine. If player shoot, the gun will deduct 1 bullets.

  • If the gun become 0 bullet, the gun cannot shoot. Player can cost 1 magazine to recover bullets.

  • Jump to any board can get 1 score, kill AI character can get 5 scores and kill player can get 10 scores.

  • The board will rise up, player needs to control the character walks down to different board quickly, and there are different effects in different board.

  • The boards will generate randomly.

  • If player's death count lower than 0, kill count more than 10+(5* elapsed second), score more than 100+(50* elapsed second) or playing in illegal scene will be defined as cheater.

  • Cheater will be disqualified, please do not try to cheat.

Game Objects

  • Normal board

  • Conveyor board: Player will be pushed to different direct.

  • Jump board: Player will high jump when he touches this board.

  • Destroy board: After player steps on this board, the board will be destroyed.

  • Spike board: HP will be deducted 20.

  • *Fall board: The board will fall when player shoot the board.

  • *Scale board:  After player steps on this board, the area of board will start increasing.

  • *Fortress: Player can get protect and hide myself at the Fortress. It also can continuous provide score and HP recover to player.

  • *Glass Fortress: Most of function same as Fortress and player can see outside of the Fortress. But other player can see inside of the Glass Fortress. 

  • *Blaster: The blaster will be randomly set at Normal board. They will shoot bullet each 2 second, attack power equal to 25.

*: Original object

Essence Code(C#)

Born the boards

float time;
public float x;
public float y;
public float z;
public int rand;
public int randquad;
public int timeset;
int i;
float j;
float k;

    // Use this for initialization
    void Start () {
       time = 0;
   }
   
   // Update is called once per frame
   void Update () {
       i = Random.Range(0, 13);
        if (randquad==0)
        {
            j = Random.Range(-6, 7) * 2;
            k = Random.Range(1, 7) * 2;
        }
        if (randquad == 1)
        {
            j = Random.Range(-6, 7) * 2;
            k = Random.Range(-6, 0) * 2;
        }


        if (time <= 0) {
           if (i == 1) {
                PhotonNetwork.InstantiateRoomObject("normal", new Vector3 (j, y, k), Quaternion.identity);
               time = timeset;
           }
           if (i == 2) {
                PhotonNetwork.InstantiateRoomObject("spike", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           } 
           if (i == 3) {
                PhotonNetwork.InstantiateRoomObject("scale", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           } 
           if (i == 4) {
                PhotonNetwork.InstantiateRoomObject("normalBlaster", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           } 
           if (i == 5) {
                PhotonNetwork.InstantiateRoomObject("direct", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           } 
           if (i == 6) {
                PhotonNetwork.InstantiateRoomObject("direct3", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           } 
           if (i == 7) {
                PhotonNetwork.InstantiateRoomObject("destory", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           } 
           if (i == 8) {
                PhotonNetwork.InstantiateRoomObject("fall", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           } 
           if (i == 9) {
                PhotonNetwork.InstantiateRoomObject("fortress", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           } 
           if (i == 10) {
                PhotonNetwork.InstantiateRoomObject("glassfortress", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
           }
            if (i == 11)
            {
                PhotonNetwork.InstantiateRoomObject("jump", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
            }
            if (i >= 12)
            {
                PhotonNetwork.InstantiateRoomObject("normal", new Vector3(j, y, k), Quaternion.identity);
                time = timeset;
            }

        }
        else {
                   time -= Time.deltaTime;
               }
}
}

Player coding

using Photon.Pun;
using Photon.Realtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Hashtable = ExitGames.Client.Photon.Hashtable;

public class PlayerController : MonoBehaviourPunCallbacks, IDamageable
{
   [SerializeField] Image healthbarImage;
   [SerializeField] GameObject ui;

    [SerializeField] GameObject cameraHolder;
    [SerializeField] GameObject barrer;

    [SerializeField] float mouseSensitivity, sprintSpeed, walkSpeed, jumpForce, smoothTime;

    [SerializeField] Item[] items;

    [SerializeField] Text killt;
    [SerializeField] Text deatht;
    [SerializeField] Text scoret;
    [SerializeField] Text hight;

    [SerializeField] Text bullet;
    [SerializeField] Text bulletbox;

    [SerializeField] Text topt;
    [SerializeField] Text bottomt;

    [SerializeField] Text hname;

    int itemIndex;
   int previousItemIndex = -1;

    float verticalLookRotation;
   bool grounded;
   Vector3 smoothMoveVelocity;
   Vector3 moveAmount;

    Rigidbody rb;

    PhotonView PV;

    const float maxHealth = 100f;
   float currentHealth = maxHealth;

    const float maxBullet = 6f;
    float currentBullet = maxBullet;

    float BulletBox = 3f;

    double top;
    double bottom;

    bool Invincible=true;
    float InvincibleCdown = 5f;

    PlayerManager playerManager;
    public Animator animator;

    void Awake()
   {
       rb = GetComponent<Rigidbody>();
       PV = GetComponent<PhotonView>();

        playerManager = PhotonView.Find((int)PV.InstantiationData[0]).GetComponent<PlayerManager>();
   }

    void Start()
   {
       if(PV.IsMine)
       {
           EquipItem(0);
            Cursor.lockState = CursorLockMode.Locked;
            Cursor.visible = false;
            topt.text = "";
            bottomt.text = "";
            hight.text = "0";
            hname.text = "";
            animator = GetComponent<Animator>();
        }
       else
       {
           Destroy(GetComponentInChildren<Camera>().gameObject);
           Destroy(rb);
           Destroy(ui);

        }
   }

    void Update()
   {
       if(!PV.IsMine)
           return;
       Look();
       Move();
       Jump();

        top = 67.5 - transform.position.y;
        bottom = transform.position.y-10;

        if (Input.GetKey(KeyCode.W)|| Input.GetKey(KeyCode.A)|| Input.GetKey(KeyCode.S)|| Input.GetKey(KeyCode.D))
        {
            animator.SetBool("Walk", true);
        }
        else
        {
            animator.SetBool("Walk", false);
        }

        for (int i = 0; i < items.Length; i++)
       {
           if(Input.GetKeyDown((i + 1).ToString()))
           {
               EquipItem(i);
               break;
           }
       }

        if(Input.GetAxisRaw("Mouse ScrollWheel") > 0f)
       {
           if(itemIndex >= items.Length - 1)
           {
               EquipItem(0);
           }
           else
           {
               EquipItem(itemIndex + 1);
           }
       }
       else if(Input.GetAxisRaw("Mouse ScrollWheel") < 0f)
       {
           if(itemIndex <= 0)
           {
               EquipItem(items.Length - 1);
           }
           else
           {
               EquipItem(itemIndex - 1);
           }
       }

        if(Input.GetMouseButtonDown(0) && grounded)
       {
            if (currentBullet>0) {
                items[itemIndex].Use();
                currentBullet -= 1;
            }
       }
        if (Input.GetKeyDown(KeyCode.R) && grounded&& BulletBox>0)
        {
            currentBullet = maxBullet;
            BulletBox -= 1;
        }
        if (transform.position.y < 10f|| transform.position.y > 67.5f) // Die if you fall out of the world
       {
           Die();
       }

        killt.text = PlayerManager.killrec.ToString();
        deatht.text = PlayerManager.deathsrec.ToString();
        scoret.text = PlayerManager.scorerec.ToString();
        hight.text = ScoreboardItem.high.ToString();
        hname.text = ScoreboardItem.highname.ToString();

        bullet.text = currentBullet.ToString();
        bulletbox.text = BulletBox.ToString();

        topt.text = top.ToString();
        bottomt.text = bottom.ToString();

        

        if (InvincibleCdown<=0)
        {
            Invincible = false;
            barrer.transform.localScale = new Vector3(0, 0, 0);
        }
        else
        {
            InvincibleCdown -= Time.deltaTime;
        }


            

        

    }

    void Look()
   {
       transform.Rotate(Vector3.up * Input.GetAxisRaw("Mouse X") * mouseSensitivity);

        verticalLookRotation += Input.GetAxisRaw("Mouse Y") * mouseSensitivity;
       verticalLookRotation = Mathf.Clamp(verticalLookRotation, -90f, 90f);

        cameraHolder.transform.localEulerAngles = Vector3.left * verticalLookRotation;


    }

    void Move()
   {
       Vector3 moveDir = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;

        moveAmount = Vector3.SmoothDamp(moveAmount, moveDir * (Input.GetKey(KeyCode.LeftShift) ? sprintSpeed : walkSpeed), ref smoothMoveVelocity, smoothTime);
        
    }

    void Jump()
   {
       if(Input.GetKeyDown(KeyCode.Space) && grounded)
       {
           rb.AddForce(transform.up * jumpForce);
       }
   }

    void EquipItem(int _index)
   {
       if(_index == previousItemIndex)
           return;

        itemIndex = _index;

        items[itemIndex].itemGameObject.SetActive(true);

        if(previousItemIndex != -1)
       {
           items[previousItemIndex].itemGameObject.SetActive(false);
       }

        previousItemIndex = itemIndex;

        if(PV.IsMine)
       {
           Hashtable hash = new Hashtable();
           hash.Add("itemIndex", itemIndex);
           PhotonNetwork.LocalPlayer.SetCustomProperties(hash);
       }
   }

    public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
   {
       if(changedProps.ContainsKey("itemIndex") && !PV.IsMine && targetPlayer == PV.Owner)
       {
           EquipItem((int)changedProps["itemIndex"]);
       }
   }

    public void SetGroundedState(bool _grounded)
   {
       grounded = _grounded;
   }

    void FixedUpdate()
   {
       if(!PV.IsMine)
           return;

        rb.MovePosition(rb.position + transform.TransformDirection(moveAmount) * Time.fixedDeltaTime);
   }

    public void TakeDamage(float damage)
   {
       PV.RPC(nameof(RPC_TakeDamage), PV.Owner, damage);
   }

    [PunRPC]
   void RPC_TakeDamage(float damage, PhotonMessageInfo info)
   {
        if (Invincible==false)
        {
            currentHealth -= damage;
        }

        healthbarImage.fillAmount = currentHealth / maxHealth;

        if(currentHealth <= 0)
       {
            PlayerManager.Find(info.Sender).GetKill();
            Die();
           
       }
   }

    void OnTriggerEnter(Collider other)
    {
        if (!PV.IsMine)
            return;

        if (other.gameObject.tag == "board")
        {
            currentHealth += 5;
            if (currentHealth > maxHealth)
            {
                currentHealth = maxHealth;
            }
            healthbarImage.fillAmount = currentHealth / maxHealth;
            playerManager.EachSec();
        }

        if ((other.gameObject.name == "spike") || (other.gameObject.name == "spike(Clone)"))
        {
            if (Invincible == false)
            {
                currentHealth -= 20;
            }
            healthbarImage.fillAmount = currentHealth / maxHealth;

            if (currentHealth <= 0)
            {
                Die();
            }
        }

        if (other.gameObject.name == "Bullet" || other.gameObject.name == "Bullet(Clone)")
        //Object name is the name of the GameObject you want to check for collisions with.
        {
            if (Invincible == false)
            {
                currentHealth -= 25;
            }
            healthbarImage.fillAmount = currentHealth / maxHealth;

            if (currentHealth <= 0)
            {
                Die();
            }

        }

        if (other.gameObject.name == "jump" || other.gameObject.name == "jump(Clone)")
        //Object name is the name of the GameObject you want to check for collisions with.
        {
            rb.AddForce(transform.up * jumpForce);

        }

    }

    void Die()
   {
       playerManager.Die();
   }
}

bottom of page