The story of winning the third place as an intern of CyberAgent!

28 minute read

Introduction

Nice to meet you. My name is Deko and I am studying Unity, C #, and C ++. Since this is Qiita’s first post, I would appreciate it if you could tell me if there are any improvements not only in C # and Unity, but also in how to write Qiita.

For the first post, I participated in CyberAgent’s 3-day game development intern “Protosprint League”, so I would like to write about it.
I hope this article will be of some help to you if you are going to join CyberAgent’s game engineer internship.

Target audience

Those who are planning to participate in CyberAgent’s internship from now on
Those who are interested in CyberAgent’s internship
Those who want to know what a corporate intern is like as a game engineer

table of contents

    1. Internship overview
  1. Planning
    1. Character control
  2. Level design
  3. Result scene. Title scene
  4. Complete
  5. Comment
  6. Looking back on the entire intern
  7. Finally

1. 1. Internship overview

It was a three-day internship from 9/19 to 9/21. There were four members, but three are client engineers. One was a server engineer.
I participated in this CA-sama internship last year as well, but it was a difficult place because there was no part of connecting to this server last year.

First, before the internship started, we talked with the members at ZOOM to discuss what the game would look like. The theme is “shooting game” or “puzzle game”
When I talked with ZOOM, I got to know everyone on the team very well and even though it was only a few hours since I started talking, I was able to talk about various topics.
Now that it’s over, this friendship has become the greatest weapon of our team, and I think that thanks to that, we were able to make a convincing game in a short time of 3 days and win the approval. ..

2. Planning

As for the planning, one game will be made in just three days, so I want to decide in advance. For those who will participate in the future, I hope that the stage of this project will be completed before the internship begins. I think it’s good to write specifications.

First of all, we decided to create a “shooting game” with twists and turns. (If you draw the twists and turns, the story will be doubled, so I will stop it (laughs))

After that, I thought about whether it was 3D or 2D. I released my own game on the App Store last year and it’s a 3D shooter. What I struggled with here was the difficulty of the operation called 3D shooting.
3D is natural, but it has three dimensions. This means that operating all of these requires at least three x-axis, y-axis, and z-axis operations. Since my game is an iPhone, I operate an airplane with my left hand. Depress the bullet with your right hand. Because I wanted to make it a specification, the operation would be two-dimensional. Therefore, I made the aircraft automatically move in the z-axis, that is, in the forward direction.

So what do you do this time? PUBG, which I often play, has a three-dimensional world and two-dimensional operations. Of course there are jumps, but they will fall due to gravity.
The game I made this time was planned to be WEBGL running on a PC. Therefore, for 3D operation, you have to press at least 3 buttons on the keyboard, which makes the user more complicated than fun. I felt that.

In my opinion, it is natural to make people think that it is “fun”, but first you have to play it. And if the operation was too difficult in the first play, I thought, “I think I can do various things in this game, but it’s complicated and I don’t understand, so I’ll play it later.” Especially in this hackathon, there are 8 teams including our team (Team A). Considering that mentors will play eight games in a short amount of time, we have to create something that will draw us in the first play.

Based on the above ideas, we first considered the slogan. ** “Easy, easy and fun” **.
Being “easy” will thoroughly lower the threshold of play. Even people who are not familiar with the game can enjoy it because the “operation” is easy. And by sticking to effects, SE, and level design, it makes players think “interesting”. I want to play it again! It makes me feel. Therefore, being “easy” will lead to having you play again. By shortening the time of one play to some extent, you can play many times in a short time. I thought about doing various things to make this circulation cycle. And this slogan doesn’t break anything. In other words, I thought that I should stop adding game specifications that go against this slogan.

So what can we do to achieve this slogan? First, I noticed that the operation is “easy”. Even elementary school girls who are not used to playing games can easily operate it. With this in mind, even WASD operations may no longer be cumbersome. I thought like that.
And it may be difficult to operate both hands. I came up with the idea. However, if you can operate it with one hand, you have to hold the mouse or one button on the keyboard (I decided that even pressing two buttons would be complicated). Can you implement a shooting game with this? I was worried so.

Shooting games are player controls and bullets. At least these two are required. Can these be operated with one hand?
With this in mind, no answer was given, and the team meeting that started at 21:30 was about to exceed the date.
I took a restroom break to take a break. At that time, I was wondering if there was any good idea.

The ideas we have been thinking about have something in common. that is
** Let’s borrow the idea of a selling game and add original elements to it to make a seemingly new game **
It was like that.
I think this idea is correct. Why are the games that are selling right now so interesting? Thinking about this is the first step in making an interesting game.
However, I was thinking so long that I decided to move away from this idea once.
However, it is difficult to create a new game from scratch, so what should we do?

So think again from a different angle
** Can you get an idea from attractions such as theme parks **?
I came up with the idea.
The theme park is the same entertainment. There is something in common in entertaining people. Then, when I thought that there was something for shooting at the attraction, I suddenly came up with the “Buzz Lightyear” in Disneyland.

The Buzz Lightyear attraction is to get on the plane and when you find the target, rotate the plane and shoot at the target! !! !! That’s it. But it’s so interesting. I always ride when I go to Disney. It’s amazing how interesting it is, even though there are two operations, “rotate” and “shoot”. Besides, the points are ranked, and I thought that part could also be used for connecting to the server. Furthermore, when I was riding the Buzz Lightyear, I thought that when I looked back at the many targets, there was a target with the highest score, and level design was also considered tremendously in terms of hidden elements. is.

Considering these, if all movements can be done automatically like Buzz Lightyear, the player has only two operations: rotation and shooting. And by making it a mouse operation
** Move the mouse to move the viewpoint **
** Click to get a bullet **
If you do this, you can achieve the one-handed operation that was originally your goal.
After that, if you change the score according to the type of target like Buzz Lightyear and stick to the level design by devising the position of the target etc., you can make something that can fully aim for the prize! I thought.

If you tell this story to other members, everyone will agree and let’s go! Since it was solidified like that, the general game specifications have been decided.

Also, I ideally skip the tutorial. I think that way (that’s how easy it is). Then, without creating a tutorial scene, if you place a target on the game start scene and click on it, it will transition to the game scene, but if you shoot a bullet there, the player will “click to hit the bullet” “ I thought that it could be recognized as “use a mouse”.
It means that the title scene doubles as a tutorial scene.

3. 3. Character control

I’m finally talking about programming (laughs) I’m sorry to talk for a long time.
I was in charge of player control and creation of enemies (targets). Stage creation. Title scenes, result scenes and UI. It is the basis of the game such as scene transitions. First of all, I will talk about player control, which can be said to be the cornerstone of the game.

About viewpoint rotation with mouse


public float sensitivity = 30f;
public GameObject cam;
float rotX, rotY;


void Update(){

rotX = Input.GetAxis("Mouse X") * sensitivity;
rotY = Input.GetAxis("Mouse Y") * sensitivity;

CameraRotate(cam, rotX, rotY);
}

Now you can easily rotate the viewpoint by moving the mouse. And the essential CamRotate method is



private void CameraRotate(GameObject cam, float rotX, float rotY)
{
    transform.Rotate(0, rotX * Time.deltaTime, 0);
    cam.transform.Rotate(-rotY * Time.deltaTime, 0, 0);
}

After writing these two, the viewpoint rotation is over. Change the sensitivity to the value you like. After all, I stayed at 30, but on the Editor I wrote the Start method to adjust it to multiply by 1.5.

Click to eject bullets

Next is the firing of the main part of the shooting game.
As I mentioned in the planning section, it is a specification that shoots bullets with a click. Also, at the beginning of the image of Buzz Lightyear, the stage was an image of indoors (after all, it became outdoors (laughs) that story is a part of level design), so it was assumed that there was a wall around it, so in the direction you clicked I thought that I should aim the firing vector of the bullet to the position where I flew Ray and hit it, so I got the following code.


void Update(){

            if (Input.GetMouseButtonDown(0))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 1000))
                {
                    Vector3 dir = hit.point - bulletEnter.position;
                    dir = dir.normalized;
                    LaserLaunch(dir);

                    if (hit.transform.tag == Point1)
                    {
                        //The processing when the bullet hits is written below
                        Debug.Log("Point!");
                        
                    }
                    
                }
            }
}

private void LaserLaunch(Vector3 direction)
    {
        GameObject laser = Instantiate(laserBullet,bulletEnter.position,Quaternion.identity);
        Rigidbody laserRigid = laser.GetComponent<Rigidbody>();
        laserRigid.AddForce(direction * laserSpeed);
    }

Now you have fired a bullet. If you use Physics.Raycast, you might think that if you don’t have a Collider where you clicked outdoors, you wouldn’t be able to shoot a bullet. I think too (laughs)
I can’t shoot when I’m outdoors when I’m really writing code. I thought, but even if it is changed to the outdoors, the specification that the player moves automatically does not change, so I thought about putting an invisible wall around the route and wrote it with an emphasis on speed. It was.
As a result, it became outdoors, so I responded with an invisible wall.

Also, you may have thought that even though it is a shooting game, it does not have collision detection with bullets. This game is automatic for the player, and the fruits that will come out later are also moving. In the meantime, I wanted the bullets to hit as much as possible, so I took a hit judgment when clicking, and thought about making the bullet speed so fast that it would not overturn that judgment.
Isn’t it hit! This is because I wanted to make the specifications easier to hit than it is said.

Point1 and laserSpeed are written in the field in SerializeField format so that they can be operated by the inspector.
After all, it is easier to write the tag processing of collision detection as a variable instead of writing it directly with tag == “”. At this time, I didn’t know if the target was an apple or a carrot, so even if the number of apples, carrots, grapes, watermelons, etc. increased later, I could just change the variables.

With this code, all the user operations are done, and all that is left is the automatic movement of the player, but it does not go beyond the stage. At this point, I moved to creating a stage.
It took an hour and a half to make it. I wonder if the sense of speed was good now. In terms of letting other members imagine the atmosphere of the operation by finishing the player operation to some extent.

Implementation of targets (fruits)

Next is the implementation of the fruits that are the target.
CA Proto A - GameScene_DAIKI - WebGL - Unity 2019.4.4f1 Personal _DX11_ 2020_09_29 20_50_10.png
I made the behavior of the enemies with Animation as above. It’s too difficult later! I received the opinion that I slowed down the movement a little and increased the size of the target.
I moved this by changing the animation with apples, carrots, grapes, bananas, and watermelons.

Game manager implementation

It’s about time you need a script to keep your score and game status. I decided to create a game manager and manage it.


public class GAME_MANAGER : MonoBehaviour
{
   
    //0th is apple, 1st is carrot, 2nd is banana, 3rd is watermelon
    public int[] fruitCount = { 0, 0, 0, 0,0 };

    [SerializeField] int appleTimes = 1;
    [SerializeField] int carrotTimes = 5;
    [SerializeField] int bananaTimes = 10;
    [SerializeField] int grapeTimes = 30;
    [SerializeField] int watermelonTimes = 50;

    public void GameInitialize()
    {
        PointTimes = 0;
        
        for(int i = 0; i < fruitCount.Length; i++)
        {
            fruitCount[i] = 0;
        }
    }
    public void PointTimesUp()
    {
        PointTimes++;
        Debug.Log(PointTimes);
    }

    public void PointTimesDecrease()
    {
        PointTimes--;
    }

    public void AppleUp()
    {
        int number = fruitCount[0];
        number++;
        fruitCount[0] = number;
        Debug.Log("Apple" + fruitCount[0]);
    }

    public void CarrotUp()
    {
        int number = fruitCount[1];
        number++;
        fruitCount[1] = number;
        Debug.Log("Carrot" + fruitCount[1]);
    }

    public void BananaCount()
    {
        int number = fruitCount[2];
        number++;
        fruitCount[2] = number;
        Debug.Log("Banana" + fruitCount[2]);
    }

    public void WaterMelonUp()
    {
        int number = fruitCount[3];
        number++;
        fruitCount[3] = number;
        Debug.Log("WaterMelon" + fruitCount[3]);
    }

    public void GrapeUp()
    {
        int number = fruitCount[4];
        number++;
        fruitCount[4] = number;
        Debug.Log("Grape" + fruitCount[4]);

    }

    public int CaliculateScore()
    {
        int score = fruitCount[0] * appleTimes + fruitCount[1] * carrotTimes + fruitCount[2] * bananaTimes + fruitCount[3] * watermelonTimes+fruitCount[4]*grapeTimes;

        return score;
    }


}

Here, the completed form is displayed above at once.
I decided to keep an array called fruitCont and count up the numbers in that array one by one by collision detection. I wish I had a persistent code around here too …
The score will change depending on the apple, carrot, and banana, so use [Serialize Field] to play with it from the inspector.
Apple Times and carrot Times are the magnifications.
At the time of collision detection, AppleUp () is called when it hits an apple, CarrotUp () is called when it hits a carrot, and CaliculateScore () is called when the game is cleared, and the score is converted.

Player script fix

Since the Game Manager has been created so far, I changed the script on the Player side and added the cooperation with the game manager.
First, here is the full text of the Update function:


 void Update()
    {

        if (gameState == GameState.CountDown)
        {
            countDeltaTime += Time.deltaTime;

            if (countDeltaTime <= 1)
            {
                if (!CountDownText.gameObject.activeSelf)
                {
                    CountDownText.gameObject.SetActive(true);
                }
                CountDownText.text = "3";
            }
            else if(countDeltaTime<=2)
            {
                CountDownText.text = "2";
            }else if (countDeltaTime <= 3)
            {
                CountDownText.text = "1";
            }else

            if (countDeltaTime >= countInterval)
            {
                countDeltaTime = 0;
                CountDownText.text = "Go!";
                animator.enabled = true;
                gameState = GameState.InGame;
                StartCoroutine(DisappearObject(CountDownText.gameObject, 1));
            }

        }else

        if (gameState == GameState.InGame)
        {
            if (!progressSlider.gameObject.activeSelf)
            {
                progressSlider.gameObject.SetActive(true);
            }

            AnimatorStateInfo animInfo = animator.GetCurrentAnimatorStateInfo(0);


            if (animInfo.normalizedTime < 1)
            {
                progressSlider.value = (animInfo.normalizedTime);
            }

            if (animInfo.normalizedTime >= 1)
            {
                //End processing
                ResultMethod();
            }

            rotX = Input.GetAxis("Mouse X") * sensitivity;
            rotY = Input.GetAxis("Mouse Y") * sensitivity;

            CameraRotate(cam, rotX, rotY);



            if (Input.GetMouseButtonDown(0))
            {
                //Vector3 clickPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
                //clickPos.z = transform.position.z + transform.forward.z * 1000;
                //Vector3 dir = clickPos - bulletEnter.position;

                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 1000))
                {
                    Vector3 dir = hit.point - bulletEnter.position;
                    dir = dir.normalized;
                    LaserLaunch(dir);

                    shotSE.Play();

                    if (hit.transform.tag == Point1)
                    {
                        //Processing when a bullet hits
                        Debug.Log("Point!");
                        //manager.PointTimesUp();
                        manager.AppleUp();
                        HitParticle(hit, 0);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));
                    }
                    else if (hit.transform.tag == Point2)
                    {
                        manager.CarrotUp();
                        HitParticle(hit, 1);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));
                    }
                    else if (hit.transform.tag == Point3)
                    {
                        manager.BananaCount();
                        HitParticle(hit, 2);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));

                    }
                    else if (hit.transform.tag == Point4)
                    {
                        manager.WaterMelonUp();
                        HitParticle(hit, 3);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));
                    }else if (hit.transform.tag == Point5)
                    {
                        manager.GrapeUp();
                        HitParticle(hit, 4);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));                           

                    }
                }


            }

            
        }else

        if (gameState == GameState.GameClear)
        {

            if (Input.GetMouseButtonDown(0))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 1000))
                {
                    Vector3 dir = hit.point - bulletEnter.position;
                    dir = dir.normalized;
                    LaserLaunch(dir);

                    if (hit.transform.tag == "ResultPoint")
                    {
                        StartCoroutine(GoToResult());
                    }
                }


            }

        }
    }

Let’s take a look at this in part.


if (gameState == GameState.CountDown)
        {
            countDeltaTime += Time.deltaTime;

            if (countDeltaTime <= 1)
            {
                if (!CountDownText.gameObject.activeSelf)
                {
                    CountDownText.gameObject.SetActive(true);
                }
                CountDownText.text = "3";
            }
            else if(countDeltaTime<=2)
            {
                CountDownText.text = "2";
            }else if (countDeltaTime <= 3)
            {
                CountDownText.text = "1";
            }else

            if (countDeltaTime >= countInterval)
            {
                countDeltaTime = 0;
                CountDownText.text = "Go!";
                animator.enabled = true;
                gameState = GameState.InGame;
                StartCoroutine(DisappearObject(CountDownText.gameObject, 1));
            }

        }

This is the part of the UI that displays the countdown before the game starts.
GameState is defined as an enum. When GameState is Countdown, start the timer and display it in the text.
And when the countInterval (3 seconds here) is exceeded, the GameState is switched.
When you run it, it looks like thisMovie & TV 2020_09_29 23_33_49.png
映画 & テレビ 2020_09_29 23_36_07.png

It’s hard to understand because it’s a photo, but the UI has an Animation that fades over time.
By the way, it is used here


DisappearObject(Argument 1,Argument 2)

Is a method I often make and it’s very easy, but I’ve prepared it because some members may not be familiar with Unity.
This is the process of erasing an object after a few seconds. Originally I didn’t plan to touch the UI, so I thought I’d write a method in advance that I often use when passing scripts to someone other than myself, but it’s awkward to write.


private IEnumerator DisappearObject(GameObject disObject, float time)
    {

        yield return new WaitForSeconds(time);

        disObject.SetActive(false);

    }

Well, at the end I was the one who was saved by using it (laughs)

Next is the process after the countdown is over.


if (gameState == GameState.InGame)
        {
            if (!progressSlider.gameObject.activeSelf)
            {
                progressSlider.gameObject.SetActive(true);
            }


            AnimatorStateInfo animInfo = animator.GetCurrentAnimatorStateInfo(0);


            if (animInfo.normalizedTime < 1)
            {
                progressSlider.value = (animInfo.normalizedTime);
            }

            if (animInfo.normalizedTime >= 1)
            {
                //End processing
                ResultMethod();
            }

            //From here on, the bullet firing part
            

First is the first half. This part was implemented by incorporating the opinions of the members that it is better to show the progress of the game with some kind of UI.
I’m glad that this branch of GameState prevented me from getting impatient as soon as the game started, and I was able to prevent myself from earning points by shooting bullets before the game.

The degree of progress is shown in Slider. It’s the upper right of the picture below. It is on the bottom of the score.
映画 & テレビ 2020_09_29 23_53_59.png

Since the player is moved by Animation, first, get AnimatorStateInfo every frame.


 AnimatorStateInfo animInfo = animator.GetCurrentAnimatorStateInfo(0);

After that, check if the game is over with normalizedTime, which shows the current progress when the entire AnimatorStateInfo is set to one. The normalizedTime becomes 1 when the game is over. Less than 1 means that the game is in progress, and the progress is reflected in Slider.


if (animInfo.normalizedTime < 1)
   {
        progressSlider.value = (animInfo.normalizedTime);
   }

The second half. Since the tag is used to determine where the score changes depending on the process of skipping the Ray and sounding the SE or the hit target, the conditional branch there is accessing each score process of the manager (GameManager script instance).


             if (Input.GetMouseButtonDown(0))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 1000))
                {
                    Vector3 dir = hit.point - bulletEnter.position;
                    dir = dir.normalized;
                    LaserLaunch(dir);

                    shotSE.Play();

                    if (hit.transform.tag == Point1)
                    {
                        //Processing when a bullet hits
                        Debug.Log("Point!");
                        manager.AppleUp();
                        HitParticle(hit, 0);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));
                    }
                    else if (hit.transform.tag == Point2)
                    {
                        manager.CarrotUp();
                        HitParticle(hit, 1);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));
                    }
                    else if (hit.transform.tag == Point3)
                    {
                        manager.BananaCount();
                        HitParticle(hit, 2);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));

                    }
                    else if (hit.transform.tag == Point4)
                    {
                        manager.WaterMelonUp();
                        HitParticle(hit, 3);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));
                    }else if (hit.transform.tag == Point5)
                    {
                        manager.GrapeUp();
                        HitParticle(hit, 4);

                        ScoreDisplay.text = "Score:" + manager.CaliculateScore();

                        hit.transform.GetComponent<AudioSource>().Play();

                        StartCoroutine(DisappearObject(hit.transform.root.gameObject, 0.1f));                           

                    }
                }


            }

The last part that will clear the game


if (gameState == GameState.GameClear)
        {

            if (Input.GetMouseButtonDown(0))
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 1000))
                {
                    Vector3 dir = hit.point - bulletEnter.position;
                    dir = dir.normalized;
                    LaserLaunch(dir);

                    if (hit.transform.tag == "ResultPoint")
                    {
                        StartCoroutine(GoToResult());
                    }
                }
            }
        }

This is the same as shooting a bullet. When the game is over, a target like the one in the picture below will appear, so if you hit it, you will jump to the result scene.
映画 & テレビ 2020_09_30 0_02_43.png

As explained in the planning section, we maintain the world view by eliminating buttons as much as possible and substituting them.
The Animation of GAME CLEAR here also moved and became cute.

Up to this point, character processing is complete.

4. Level design

And as I mentioned at the planning stage, level design is indispensable for the game. Making the stage was a key part.

CA Proto A - GameScene_DAIKI - WebGL - Unity 2019.4.4f1 Personal _DX11_ 2020_09_30 0_16_22.png
This is the whole stage. I wanted to do automatic generation in terms of load, but I gave up because I didn’t have time. If I have time, I would like to try again.
The image of the stage going around and ending. It takes less than a minute and a half to play once. That is what we are aiming for, “easiness”. The world view is pop and cute. The fact that fruits have a target was perfect for brightening the world view.
The atmosphere of the team to unify this world view was solidified on the second day, which made it easier to create the stage.

CA Proto A - GameScene_DAIKI - WebGL - Unity 2019.4.4f1 Personal _DX11_ 2020_09_30 0_17_04.png
As you can see from the picture above, we have a lot of targets around the path that the player travels. Did you find out in the picture right now? Watermelon has the highest score, and apples and carrots have the lowest. Along with this, less watermelons with high scores are placed and more fruits with low scores are placed.

CA Proto A - GameScene_DAIKI - WebGL - Unity 2019.4.4f1 Personal _DX11_ 2020_09_30 0_17_23.png

Even if the game is played for less than one and a half minutes, you may get bored if you play the same thing at the same pace. The player climbs the mountain in less than a minute of play, but we set up a fever time there. By doing this, the user will not get bored and will feel the exhilaration of hitting it fully.

However, it is the apple that can only get one point that is placed a lot in the fever time so that the points do not cause inflation.
Users who want to earn points here can get high scores by shooting the grapes in the back instead of shooting the apple in front of them in Fever Time. One grape contains 30 points.

What is the implementation of two such ** “places that users who have played only a few times can enjoy” ** and ** “places that users who have played many times can enjoy” **? It was a factor that I could enjoy without getting tired of doing it.

As I mentioned earlier as a level design, I wrote it out as WEBGL about an hour and a half before I had to freeze the code, and asked the mentor and everyone on the team to play with it and give me their impressions.
At that time, I was so good at making it that I couldn’t make a decision (laughs).

The opinion received there is, first of all, the opinion that we want you to loosen the judgment. We responded to this immediately. If you don’t hit it to some extent, it won’t be a game (laughs)
The solution is to make the target bigger and slow down the movement of the enemy. It became easier for everyone later! The feeling of exhilaration has risen! I was struck by how important it was to hear the opinions of those who played this game.

After that, BGM volume and bug reports. It is recommended for participating students that they can really ** play the games they made in advance and get their opinions **! !!

sound

It’s a bit different from level design, but I’d like to talk about sound as well. I was in charge of the sound of this game. I could only do simple things because I didn’t have much time and started on the last day, but I still chose SE and BGM with particular attention.

First of all, as BGM, I choose from free materials, but of course it is a pop one that suits the world view. And considering that one play is less than one and a half minutes and it takes about 10 seconds to transition the result screen, BGM is prepared with a length of about 1:45.

Also, I especially thought about SE (sound effect when shooting a bullet and sound when the target disappears when hitting the target), but when I look for SE of “shooting” with free material, when I shoot a gun It is far from the squishy sound, the explosive sound, and the pop one, and it may destroy the world view.

In my opinion, sound is not just information from the ears, but information from the field of vision combined to determine what the sound is. In other words, what I want to say is that even if the sound when shooting a shooting gun is not the actual firing sound, if the bullet was fired before when the sound came out, the sound you heard when shooting the bullet Think of it as an SE.

From this idea, I downloaded the SE of the button sound of “pyu” and “pa” when the button was pressed, instead of the actual firing sound and explosion sound, and called it “the sound of shooting a bullet” respectively. I assigned it to “the sound when the target hits and the target disappears”.

As a result, I was able to attach a cute SE that fits the world view without any discomfort.

5. Result scene, title scene

I was also in charge of the result scene and the title scene.

Result scene

First of all, from the result scene.
映画 & テレビ 2020_09_30 0_49_41 (1).png
映画 & テレビ 2020_09_30 0_49_43.png
In this way, the specifications are such that fruits fall from the top as soon as the transition to the result scene. When I came up with it and implemented it, I suddenly shouted “Cute !!!” (laughs)

The code here looks like this:


 public void DropFruit()
    {
        for (int i = 0; i < DropCount; i++)
        {
            Vector3 randomPos = Random.insideUnitSphere * 3;
            Instantiate(Apple, DropPoint.position + randomPos, Quaternion.identity);
        }
        for (int i = 0; i < DropCount; i++)
        {
            Vector3 randomPos = Random.insideUnitSphere * 4;
            Instantiate(Banana, DropPoint.position + randomPos, Quaternion.identity);
        }

        for (int i = 0; i < DropCount; i++)
        {
            Vector3 randomPos = Random.insideUnitSphere * 5;
            Instantiate(Carrot, DropPoint.position + randomPos, Quaternion.identity);
        }

        for (int i = 0; i < DropCount; i++)
        {
            Vector3 randomPos = Random.insideUnitSphere * 6;
            Instantiate(WaterMelon, DropPoint.position + randomPos, Quaternion.identity);
        }

        for (int i = 0; i < DropCount; i++)
        {
            Vector3 randomPos = Random.insideUnitSphere * 8;
            Instantiate(Grape, DropPoint.position + randomPos, Quaternion.identity);
        }

    }

If you call Drop () as a method, fruits will be generated automatically and the fruits will fall by themselves because they are turned on to useGravity with RigidBody.

There is an int type integer called DropCount in the script, and if you play with it, the number of fruits that fall will change.
Actually, I personally thought it would be interesting to increase the number of fruits that fall depending on the score, so I coded it so that I could handle it.

It is also possible to count the number by type of fruit and pass it as an array to change the number for each type of fruit that falls. It was a dreamy feeling, but I gave up because I didn’t have time to connect the scores because I was sticky until 15 minutes before the code freeze!

Title scene

The title scene looks like the following.
映画 & テレビ 2020_09_30 0_49_12.png
映画 & テレビ 2020_09_30 0_49_12 (1).png

The target fruits are from left to right. I made a cute scene that moves from right to left. I asked the person in charge of the effects to write the scores of the fruits under the fruits. (It feels great)

I heard a voice from the mentor asking me to tell me the score as a quick reference table as well as the score to move, and I put it out as a UI on the right.
The pumpkin set for the player to ride has a cute Animation that spins and jumps at the top of the mountain.

Buttons are eliminated in both the title scene and the result scene, and the target is replaced with a button. As I talked about in the planning part, I was conscious that the title screen would replace the tutorial.

By moving the fruit in the title scene, even if the game starts and the fruit moves, you can enter the play without any discomfort.

6. Complete

Overall, I was able to implement almost everything I wanted to do just before the code freeze!
I really appreciate the members! !! !!

This game was made possible thanks to the efforts of the members, such as connecting to the server and setting up the server itself.
When the effect was completed, my voice went up. Cute pop effect. The one I was looking for! !! have become.

Various implementations without giving up until the very end. Fixed. I am deeply grateful to the members who worked hard. And I am grateful to the mentors who supported me.
Thank you very much.

I thought of the game scenario as follows.

September full moon day in the fruit kingdom. A large party will be held. You were introduced to the party and a pumpkin-riding shooting competition. Fruits are dancing with a target. Let’s play with fruits aiming for a high score!

The title is “FRUITS PARTY”. I think we have created something that was really interesting and planned.
You can play the WEBGL version from the following URL. Please play with it. (Please note that the ranking function and user registration will be defective because the server has already been closed.)
http://18.181.158.83/

7. Comment

With the final announcement on the final day, we, Team A, were able to win the 3rd place! !! !! I’m happy! !! !!

As for the impressions received, we received many really happy words such as “The level design was really solid and interesting”, “The world view is pop and cute”, and “I enjoyed playing many times”. Thank you very much.
I was really happy to hear the words I was thinking about at the time of planning.

As a correction point, it is better to remove the ballistic effect of the bullet a little faster. Devise more UI to display Ranking. It was like that.
This fix was very helpful because I thought it could be incorporated not only in this game but also in the games I’m going to make.

What was good

I think that one of the factors that won the 3rd place this time was the speedy implementation. This will increase morale, and above all, the faster you play, the more images you will have.

After all, are you on good terms? The atmosphere of the team was really good, so it was an atmosphere where anyone could speak, and the relationship where we could discuss openly was good in terms of making a better game.

8. Looking back on the entire intern

Looking back on this intern, I learned and absorbed a lot of things. As an engineer, how to show the effect and the part of connecting with the server.

What kind of company is CyberAgent? It was a good opportunity to know that.
Mentors are kind to me, and when I ask a question, they tell me a lot of things.
I was able to talk about various things at the social gathering, which was very fun and helpful.

If you are reading this article and are worried about joining CyberAgent’s internship, I think you should apply once. Even if you are not a job hunter, you may be in the second or first year of college.

9. Finally

Thank you for reading this really long article. I hope this article helps someone.
I’m glad I was able to participate in this internship. Thank you to everyone who was really involved.

Last but not least, this is the game I released on the App Store last year. The URL is as follows.
https://apps.apple.com/jp/app/spacecombat/id1458949808

I’m working hard to release my next work. I would like to continue to do my best by making use of what I gained from this internship.