해피류 개발이야기 두번째 토끼는 점프중
두번째 게임을 공개합니다. 이번에는 2D 게임에 도전해봤는데, 토끼가 지구에 떨어졌는데, 달나라까지 점프해서 올라가는 내용입니다. 그냥 점프하는 게임 만들어보고 싶었고 스토리는 어거지로 붙여 넣은거 표시나지요?
아직 C#이 적응이 안되네요. 좀 고급 문법도 사용해 보고 싶은데 익숙하지 않으니 그냥 쉽게 쉽게, 예전처럼 소프트웨어개발계획서, 기본설계, 상세설계... 뭐 이런 개발 기법 다 생략하고 생각 나는데로 그때그때 개발방향 막 바꾸고 아키텍쳐 막 바꾸고.. 발코딩했습니다. 혼자서 개발하니까 이런건 좋기는 한데, 소프트웨어 개발할때 절차 다 무시해서 나중에는 팀 프로젝트하면 적응 안될것 같네요. 쓸데없는 소리가 길어졌는데, 게임은 요런식으로 동작합니다.
<게임 플레이화면>
#1 개발환경
개발툴 : Unity2D 5.6
이미지소스 : http://opengameart.org/
개발기간 : 3일
개발하면서 특이하게 어렵다거나 그런일은 없었고, 단지 재미가 없어서 몇번 갈아 엎었어요.ㅠㅠ
몇번 갈아 엎다가 그래도 별 재미 없길래 여기서 그만 ㅋㅋㅋㅋㅋㅋ
#2 스토리
토끼가 지구에 떨어졌는데, 점프해서 달나라까지 가는 내용. 그냥 어거지로 붙인거임. 점프하는 게임을 만들어보고 싶었을 뿐이에요.
#3 스크립트
게임구현에 사용한 스크립트입니다.
대충보면 아시겠지만, 별건 없어서..
타일 생성하는 GroundManager,
토끼를 컨트롤하는 Player,
다른 잡다한 것들은 게임내에서 나오는 적들이나, 아이템 정도입니다.
#4 사용된 이미지들
늘 그렇듯, 디자이너 출신이 아니기에 게임에 들어가는 디자인들은 opengameart.org 웹 사이트에 공개된 CC0 라이센스만 가져다가 사용했습니다. CC0 라이센스는 상업적이용가능하고 수정도 자유로운 라이센스입니다. 한마디로 완전 무료인거죠.
#5 개발과정
처음에는 가로로 뛰어가는 게임을 만들었는데, 생각보다 별로였네요. 그래서 과감하게 다 지우고 다시 세로로 만들었는데...
이것도 재미없고 ㅠㅠ 이왕시작한거 끝은 봐야될것 같아서 세로화면으로 해서 끝까지 만들어봤어요.
게임시작화면입니다. 소리를 켜고 끄고 정도만 있구요, 바로 게임을 시작할 수 있도록 만들었어요.
열나게 점프해서 올라가면 되어요. 화면 아래 방향키 좌/우로 움직이면 토끼는 신기하게도 좌 우로 움직이죠? ㅋㅋㅋ 그리고 점프는 알아서 됩니다. 다만 땅을 밟아야만 되겠지요.
제때 땅을 못 밟거나, 적을 만나서 부딪치면 게임오버. 달나라까지 못갔네요.
#6 소스코드
타일 생성하는 코드입니다. 토끼가 높이 올라갈 수록 점프력도 좋아지기에, 타일의 간격도 넓어지도록 코딩했어요.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GroundGenerator : MonoBehaviour {
public GameObject[] grounds;
public GameObject[] breakGrounds;
public GameObject[] shortGrounds;
public GameObject[] shortBreakGrounds;
public GameObject spring;
public GameObject jetPack;
public GameObject bronzeCoin;
public GameObject goldCoin;
public GameObject silverCoin;
private Vector2 groundAt;
private int themeId;
private GameObject player;
public int stage;
private UIManager uiManager;
// Use this for initialization
void Start () {
player = GameObject.Find("Player");
groundAt = new Vector2(0, -6.0f);
themeId = 0;
stage = 0;
uiManager = GameObject.Find("Canvas").GetComponent<UIManager>();
}
private void FixedUpdate()
{
if (player.transform.position.y > groundAt.y - 5)
{
stage += 1;
GenerateNextGround();
}
}
private void GenerateNextGround()
{
int NbOfGround = 10;
themeId = Random.Range(0, grounds.Length);
for(int i = 0; i < NbOfGround; i++)
{
RespawnItems();
RespawnGroundTile();
// next ground position
if (Random.Range(0, 2) == 0)
{
float mv = Random.Range(1.0f, 4.0f);
if (mv + groundAt.x > 5.0f)
groundAt.x -= mv;
else
groundAt.x += mv;
}
else
{
float mv = Random.Range(1.0f, 4.0f);
if (groundAt.x - mv < -5.0f)
groundAt.x += mv;
else
groundAt.x -= mv;
}
int jumpPower = uiManager.GetJumpPower();
float range = jumpPower * 0.09f;
groundAt.y += Random.Range(2.0f + range, 2.27f + range);
}
}
void RespawnGroundTile()
{
int rnd = stage;
if (stage > 6)
{
rnd = Random.Range(0, 6);
}
else
{
rnd = Random.Range(0, rnd);
}
// normal ground
GameObject toGround;
if (rnd >= 0 && rnd < 1)
toGround = Instantiate(grounds[themeId], groundAt, Quaternion.identity, transform);
else if (rnd >= 1 && rnd < 3)
toGround = Instantiate(shortGrounds[themeId], groundAt, Quaternion.identity, transform);
else if (rnd >= 3 && rnd < 5)
toGround = Instantiate(breakGrounds[themeId], groundAt, Quaternion.identity, transform);
else
toGround = Instantiate(shortBreakGrounds[themeId], groundAt, Quaternion.identity, transform);
// add spring by chance
if (Random.Range(0, 20) == 1)
{
Vector2 at = new Vector2(Random.Range(-0.5f, 0.5f) + toGround.transform.position.x, 0.75f + toGround.transform.position.y);
Instantiate(spring, at, Quaternion.identity, transform);
}
}
void RespawnItems()
{
int rnd = Random.Range(0, 15);
if (rnd < 5)
BronzeCoin();
else if (rnd >= 5 && rnd < 7)
GoldCoin();
else if (rnd >= 7 && rnd < 10)
SilverCoin();
else if (rnd == 12)
JetPack();
}
void BronzeCoin()
{
Vector2 at = new Vector2(groundAt.x + Random.Range(-1.0f, 1.0f), groundAt.y + 1.0f);
Instantiate(bronzeCoin, at, Quaternion.identity, transform);
int amount = Random.Range(2, 4);
for(int i = 0; i < amount; i++)
{
at.x += Random.Range(-3, 3);
at.y += Random.Range(-2.0f, 2.0f);
Instantiate(bronzeCoin, at, Quaternion.identity, transform);
}
}
void GoldCoin()
{
Vector2 at = new Vector2(groundAt.x + Random.Range(-1.0f, 1.0f), groundAt.y + 1.0f);
Instantiate(goldCoin, at, Quaternion.identity, transform);
int amount = Random.Range(0, 4);
for (int i = 0; i < amount; i++)
{
at.x += Random.Range(-3, 3);
at.y += Random.Range(-2.0f, 2.0f);
Instantiate(goldCoin, at, Quaternion.identity, transform);
}
}
void SilverCoin()
{
Vector2 at = new Vector2(groundAt.x + Random.Range(-1.0f, 1.0f), groundAt.y + 1.0f);
Instantiate(silverCoin, at, Quaternion.identity, transform);
int amount = Random.Range(1, 4);
for (int i = 0; i < amount; i++)
{
at.x += Random.Range(-3, 3);
at.y += Random.Range(-2.0f, 2.0f);
Instantiate(silverCoin, at, Quaternion.identity, transform);
}
}
void JetPack()
{
Vector2 at = new Vector2(groundAt.x + Random.Range(-3.0f, 3.0f), groundAt.y + Random.Range(1.0f, 2.0f));
Instantiate(jetPack, at, Quaternion.identity, transform);
}
}
타일 코드입니다. 타일은 높이 올라갈 수록 움직이기도 하고 한번 밟으면 없어지기도 하게 만들었어요.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tile : MonoBehaviour {
public Sprite damageTile;
public int breakCounter;
private int autoBreak;
private GameObject player;
private bool isMove;
private Rigidbody2D rb2d;
private float moveRate;
private float nextMove;
private float moveSpeed;
private Vector2 originPos;
// Use this for initialization
void Start ()
{
player = GameObject.Find("Player");
isMove = false;
if(GameObject.Find("GroundHolder").GetComponent<GroundGenerator>().stage > 3 && Random.Range(0, 5) == 0)
{
rb2d = GetComponent<Rigidbody2D>();
isMove = true;
originPos = transform.position;
moveSpeed = Random.Range(0, 2) == 0 ? 0.03f : -0.03f;
moveRate = 1.0f;
nextMove = Time.time + moveRate;
}
}
// Update is called once per frame
void Update ()
{
if (!isMove) return;
if(player.transform.position.y - transform.position.y > 10)
{
Destroy(gameObject);
}
if(Time.time > nextMove)
{
nextMove = Time.time + moveRate;
moveSpeed *= -1.0f;
}
rb2d.MovePosition(new Vector2(transform.position.x + moveSpeed, transform.position.y));
}
private void OnCollisionExit2D(Collision2D collision)
{
if (breakCounter <= 0) return;
if (collision.gameObject.tag == "Player")
{
breakCounter -= 1;
if(breakCounter <= 0)
{
GetComponent<Animator>().SetTrigger("FadeOut");
Destroy(gameObject, 1.0f);
SoundManager.instance.PlayTileBreakEfx();
}
else if(breakCounter <= 1)
{
GetComponent<SpriteRenderer>().sprite = damageTile;
}
}
}
}
토끼를 제어하는 코드입니다. 토끼는 모바일과 데스트탑에서 움직이는 방식이 다르기에, 입력받는 코드를 다르게 만들었어요.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
private Rigidbody2D rb2d;
private Animator anim;
public GameObject cloudParticle;
public GameObject jetPack;
public bool isForceJumping;
public float speed;
public float maxSpeed;
public float jumpGroundForce;
public float jumpSpringForce;
public float jumpJetPackForce;
public float maxJumpForce;
private bool isWalk;
private bool isJump;
private float horizontal;
// Use this for initialization
void Start () {
rb2d = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
isWalk = false;
isJump = false;
isForceJumping = false;
jetPack.SetActive(false);
horizontal = 0.0f;
Invoke("JumpByGround", 2.0f);
Invoke("RemoveGround", 2.5f);
}
// Update is called once per frame
void Update ()
{
Physics2D.IgnoreLayerCollision(8, 9, rb2d.velocity.y > 0);
#if UNITY_STANDALONE || UNITY_WEBPLAYER || UNITY_EDITOR
horizontal = Input.GetAxisRaw("Horizontal");
rb2d.AddForce(new Vector2(horizontal, 0) * speed * Time.deltaTime);
#elif (UNITY_ANDROID || UNITY_IPHONE || UNITY_WP8)
//horizontal = Input.acceleration.x * 1.5f;
rb2d.AddForce(new Vector2(horizontal, 0) * speed * Time.deltaTime);
#endif
if (Mathf.Abs(rb2d.velocity.x) > maxSpeed)
{
float max = rb2d.velocity.x > 0 ? maxSpeed : -maxSpeed;
rb2d.velocity = new Vector2(max, rb2d.velocity.y);
}
// Animator
if (horizontal > 0)
{
transform.localScale = new Vector2(1.0f, 1.0f);
isWalk = true;
}
else if (horizontal < 0)
{
transform.localScale = new Vector2(-1.0f, 1.0f);
isWalk = true;
}
else
{
isWalk = false;
}
anim.SetBool("Walk", isWalk);
if(rb2d.velocity.y < -20)
{
GameObject.Find("Canvas").GetComponent<UIManager>().GameOver(false);
}
if(isForceJumping && rb2d.velocity.y < -0.1f)
isForceJumping = false;
}
private void RemoveGround()
{
Destroy(GameObject.Find("Ground").gameObject);
}
public void JumpByGround()
{
SoundManager.instance.PlayJumpEfx();
rb2d.velocity = new Vector2(rb2d.velocity.x, 0);
rb2d.AddForce(new Vector2(0, jumpGroundForce));
isJump = true;
anim.SetBool("Jump", isJump);
PlayCloudParticle();
}
public void JumpBySpring()
{
rb2d.velocity = new Vector2(rb2d.velocity.x, 0);
rb2d.AddForce(new Vector2(0, jumpSpringForce));
isJump = true;
anim.SetBool("Jump", isJump);
PlayCloudParticle();
}
public void JumpByJet()
{
isForceJumping = true;
rb2d.velocity = new Vector2(rb2d.velocity.x, 0);
rb2d.AddForce(new Vector2(0, jumpJetPackForce));
isJump = true;
anim.SetBool("Jump", isJump);
PlayCloudParticle();
jetPack.SetActive(true);
Invoke("DeactiveJetPack", 1.0f);
}
public void MoveLeft()
{
horizontal = -1.0f;
}
public void MoveRight()
{
horizontal = 1.0f;
}
public void MoveStop()
{
horizontal = 0.0f;
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "Ground")
{
JumpByGround();
}
}
private void PlayCloudParticle()
{
Vector2 at = new Vector2(transform.position.x, transform.position.y - 1.2f);
GameObject toInstance = Instantiate(cloudParticle, at, Quaternion.identity);
toInstance.GetComponent<ParticleSystem>().Play();
Destroy(toInstance, 1.0f);
}
private void DeactiveJetPack()
{
jetPack.SetActive(false);
}
}
게임내 아이템중의 하나인 점프하는 스프링 코드입니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class JumpSpring : MonoBehaviour {
public Sprite jumpActivate;
private bool isActivated;
// Use this for initialization
void Start ()
{
isActivated = false;
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (isActivated) return;
if(collision.tag == "Player")
{
collision.GetComponent<Player>().JumpBySpring();
GetComponent<SpriteRenderer>().sprite = jumpActivate;
isActivated = true;
Invoke("InvokeFadeOut", 0.4f);
SoundManager.instance.PlaySpringEfx();
}
}
private void InvokeFadeOut()
{
GetComponent<Animator>().SetTrigger("FadeOut");
Destroy(gameObject, 1.0f);
}
}
마지막으로 적들 중에서 한 객체의 코드입니다. 나머지 적들도 동일한 방식으로 코딩했어요. 날라다니는 파리인데, 랜덤으로 움직이게 만들어봤어요. 토끼와 부딪히면 바로 게임오버~
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnermyFlying : MonoBehaviour
{
public string enermyName;
private Rigidbody2D rb2d;
private float moveRate;
private float nextMove;
private float moveRange;
private bool facingRight;
private float moveSpeed;
private Vector2 originPos;
private GameObject player;
// Use this for initialization
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
player = GameObject.Find("Player");
originPos = transform.position;
moveRate = Random.Range(4.0f, 6.0f);
moveSpeed = Random.Range(0, 2) == 0 ? 0.01f : -0.01f;
moveRange = Random.Range(2.0f, 4.0f);
facingRight = Random.Range(0,2) == 0 ? true : false;
}
// Update is called once per frame
void Update()
{
if (player.transform.position.y - transform.position.y > 10)
{
Destroy(gameObject);
}
rb2d.MovePosition(new Vector2(transform.position.x + moveSpeed, transform.position.y + moveSpeed));
if (transform.position.x > originPos.x + moveRange && facingRight)
{
moveSpeed *= -1;
facingRight = !facingRight;
}
else if (transform.position.x < originPos.x - moveRange && !facingRight)
{
moveSpeed *= -1;
facingRight = !facingRight;
}
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Player")
{
if (collision.GetComponent<Player>().isForceJumping) return;
GameObject.Find("Canvas").GetComponent<UIManager>().GameOver(true);
Destroy(gameObject);
}
}
}
#7 구글 플레이 배포
자 허접하지만, 일단 만들어봤으니 배포를 해야겠지요. 구글 플레이에 배포했구요. 토끼는 점프중 앱은 아래에서 다운 받으시면 되어요^^
궁금한점이 있거나, 잘못되었거나 개선할 부분이 있다면 가차없이 뎃글 남겨 주시면 되어요.
https://play.google.com/store/apps/details?id=com.happyryu.jumpjump&hl=ko
최근댓글