본문 바로가기
C#

[C#] Xamarin with CocosSharp 공튀기기

by 슈퍼닷 2017. 3. 4.
반응형

파일 : Content.zip

 

 

지금부터 올리는 자마린 CocosSharp 에 관한 글은 제가 배우면서 기록하기위해 씁니다.

이글이 도움되실거 같으시면 참고하셔도 됩니다. 모든건 자마린 홈페이지에서 CocosSharp 튜토리얼을 배우면서 씁니다.

---------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

CocosSharp는 2D 게임을 만들기위해 사용됩니다. 처음 시작으로는 공튀기기 게임을 만들겁니다.

 

 

이런 형식의 게임 입니다. ( 초딩때 컴퓨터실에서 하던 공튀기기 아닙니다.)

일단 비주얼 스튜디오에서 CocosSharp를 다운로드 합시다.

그런후 CocosSharp 템플릿에서 Android 또는 IOS 프로젝트를 만들어서 하면 됩니다.

저는 Android 를 선택했습니다. IOS는 아이폰 OS입니다.

 

프로젝트를 만드셨으면 글 상단에 있는 Content.zip 첨부파일을 다운받읍시다. 게임에서 쓸 공과 막대 png파일이 있습니다.

 

LoadGame

 

MainActivity.cs 파일에는 LoadGame 메소드가 있습니다. LoadGame은 CCGameView 설정을 위해 호출하는 함수입니다.

일종의 Form 초기 설정을 하는 작업을 하는곳입니다.

gameView.DesignResoultion 에 해상도를 설정할수있습니다.

contentSearchPaths 라는 List를 만들어 이미지를 찾을 파일 경로를 등록해놓고 등록이 끝나면 gameView.ContentManager.SearchPaths 에 대입합니다.

프로젝트를 만들면 이런 기본적인것들은 default 값으로 다 설정 되어있으며 이번 글에서는 이미지 추가를 위해 List에 Image를 추가해 놓을것입니다.

var contentSearchPaths = new List<string>() { "Fonts", "Sounds","Images" };

 

경로를 정해놨으니 이제 파일을 추가해야합니다.

Sound, Font, Image에 관한 파일들을 관리하는 폴더는 Content 폴더 안에 들어있습니다.

 

 

추가 - 기존항목 후 첨부파일에 Content.zip 에 있는 ball.png 와 paddle.png 를 추가합니다.

 

LoadGame에서 AddLayer 로 GameLayer을 추가하는 code가 있습니다.

GameLayer는 우리가 만들 게임 로직이 구현될 파일 입니다.

 

Game로직을 짜기전에 Visual 과 관련된 클래스들에 대해 알아봅시다.

 

CCNode - Visual 객체들의 베이스가 되는 클래스 입니다. 아래에 설명할 클래스들은 CCNode에서 파생된 클래스들 입니다.

CCScene - Visual tree 입니다. 보이지는 않지만 visual 에 관한 아이들을 관리해주는 놈입니다.

CCLayer - CCSprite 등의 Visual 객체들을 담고있는 Container 입니다. 이녀석에 이미지를 추가해서 공이나 패들을 볼수있습니다.

CCSprite - 이미지를 보여줍니다. CCSprite에 이미지를 직접 담아서 CCLayer에 올려놓으면 됩니다.

 

요약 하자면 CCSprite에 이미지 파일을 담고 CCLayer에 올려놓고 CCScene 에 추가합니다.

parent/child 관계는 대충 이렇게 됩니다. CCScene > CCLayer > CCSprite

 

 

제가 이해한 바로는 이런식으로 표현 할수 있는거 같습니다.

이제 본격적으로 이미지를 추가해봅시다.

지금부터 보는 코드들은 GameLayer.cs 에 있습니다.

 

using System;
using System.Collections.Generic;
using CocosSharp;
namespace BouncingGame
{
    public class GameLayer : CCLayer
    {
        CCSprite paddleSprite;
        public GameLayer () : base(CCColor4B.Black)
        {
            // "paddle" refers to the paddle.png image
            paddleSprite = new CCSprite ("paddle");
            paddleSprite.PositionX = 100;
            paddleSprite.PositionY = 100;
            AddChild (paddleSprite);
        }
        protected override void AddedToScene ()
        {
            base.AddedToScene ();
            // Use the bounds to layout the positioning of our drawable assets
            CCRect bounds = VisibleBoundsWorldspace;
            // Register for touch events
            var touchListener = new CCEventListenerTouchAllAtOnce ();
            touchListener.OnTouchesEnded = OnTouchesEnded;
            AddEventListener (touchListener, this);
        }
        void OnTouchesEnded (List<CCTouch> touches, CCEvent touchEvent)
        {
            if (touches.Count > 0)
            {
                // Perform touch handling here
            }
        }
    }
}

 

 

이미지추가할때 왜 new CCSprite ("paddle"); 로 해줬나? 라는 궁금증이 생깁니다.

이 code를 보면 확장자를 빼먹었습니다. CCSprite는 어느 파일이든 paddle 이라는 이름의 파일을 찾는다고합니다.

그래서 확장자를 쓰지 않은것이죠. 이런 기능을 만든이유는 다른 플랫폼에서는 확장자가 다를 수도 있기 때문이라고 합니다.

 

 

이상태에서 실행하면 이런 화면이 나올것입니다!
이제 공이나 점수를 추가해줘야겠죠.

그전에 화면 크기를 설정해 줍시다. 우리가 만들 겡미이 돌아갈 기본 설정을 해주러 갑시다.

 

void LoadGame (object sender, EventArgs e)
{
    CCGameView gameView = sender as CCGameView;

    if (gameView != null)
    {
        var contentSearchPaths = new List<string> () { "Fonts", "Sounds","Images" };
        CCSizeI viewSize = gameView.ViewSize;

        int width = 768;
        int height = 1027;
    ...

 

CocosSharp에서 만들어질 게임은 portrait 또는 landscape Orientation 에서 구동될수 있습니다.

제 생각에 portrait 모드는 세로 모드이고 landscape는 풍경이니깐 가로모드로 플레이 하는거 같습니다.

아무튼 그래서위해서 width를 height보다 작게 설정한거 겠죠.

우리는 portrait 모드로 실행할거기 때문에 이걸 설정해줘야합니다.

이 설정은 mainactivity 상단에 합니다.

 

[Activity (
    Label = "BouncingGame.Android",
    AlwaysRetainTaskState = true,
    Icon = "@drawable/icon",
    Theme = "@android:style/Theme.NoTitleBar",
    ScreenOrientation = ScreenOrientation.Portrait,
    LaunchMode = LaunchMode.SingleInstance,
    MainLauncher = true,
    ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden)
]
public class MainActivity : Activity
{
...

 

[Activity] 부분을 위에 처럼 고치면 됩니다.

ScreenOrientation = ScreenOrientation.Portrait 이 핵심이 되겠네요.

 

이제 공에 관한 CCSprite를 추가 합시다. ( GmaeLayer )

public class GameLayer : CCLayerColor
{
    CCSprite paddleSprite;
    CCSprite ballSprite;
    public GameLayer () : base (CCColor4B.Black)
    {
        // "paddle" refers to the paddle.png image
        paddleSprite = new CCSprite ("paddle");
        paddleSprite.PositionX = 100;
        paddleSprite.PositionY = 100;
        AddChild (paddleSprite);
        ballSprite = new CCSprite ("ball");
        ballSprite.PositionX = 320;
        ballSprite.PositionY = 600;
        AddChild (ballSprite);
    }

 

점수에관한 라벨도 설정해줘야 겠죠. 라벨은 CCLabel을 이용합니다.

 

public class GameLayer : CCLayerColor
{
    CCSprite paddleSprite;
    CCSprite ballSprite;
    CCLabel scoreLabel;
    public GameLayer () : base (CCColor4B.Black)
    {
        // "paddle" refers to the paddle.png image
        paddleSprite = new CCSprite ("paddle");
        paddleSprite.PositionX = 100;
        paddleSprite.PositionY = 100;
        AddChild (paddleSprite);
        ballSprite = new CCSprite ("ball");
        ballSprite.PositionX = 320;
        ballSprite.PositionY = 600;
        AddChild (ballSprite);
        scoreLabel = new CCLabel ("Score: 0", "Arial", 20, CCLabelFormat.SystemFont);
        scoreLabel.PositionX = 50;
        scoreLabel.PositionY = 1000;
        scoreLabel.AnchorPoint = CCPoint.AnchorUpperLeft;
        AddChild (scoreLabel);

        Schedule (RunGameLogic);
    }
    ...

 

아 설명 안한부분이 있는데 AddChild를 통해 객체들을 추가해야 보입니다.

 

지금까지 우리는 비주얼 적인 부분에 관한 code를 짰습니다.

그렇다면 우리는 이제 Game 로직에 관한 부분을 짜야합니다.

Logic을 짜면 Schedule을 통해 로직을 실행 해야합니다.

Schedule은 수직동기화 방식으로 실행한다고 합니다.

( 수직동기화는 프레임을 하나의 값으로 고정시키고 출력하는 방식입니다.자세한건 검색)

 

 

이제 우리는 RunGameLogic 에 관한 부분에 대해 짜야겠군요.

 

GameLayer class 에

float ballXVelocity;
float ballYVelocity;

 

를 추가합시다. ball의 X방향의 속도와 ballY방향의 속도를 관리하는 변수들입니다.

const float gravity = 140; 중력값은 140으로 상수설정 합시다.

또 , 점수를 보여줄 score 변수를 추가합시다.

 

int score;

 

void RunGameLogic(float frameTimeInSeconds)
{
    // This is a linear approximation, so not 100% accurate
    ballYVelocity += frameTimeInSeconds * -gravity;
    ballSprite.PositionX += ballXVelocity * frameTimeInSeconds;
    ballSprite.PositionY += ballYVelocity * frameTimeInSeconds;
    // New Code:
    // Check if the two CCSprites overlap...
    bool doesBallOverlapPaddle = ballSprite.BoundingBoxTransformedToParent.IntersectsRect(
        paddleSprite.BoundingBoxTransformedToParent);
    // ... and if the ball is moving downward.
    bool isMovingDownward = ballYVelocity < 0;
    if (doesBallOverlapPaddle && isMovingDownward)
    {
        // First let's invert the velocity:
        ballYVelocity *= -1;
        // Then let's assign a random value to the ball's x velocity:
        const float minXVelocity = -300;
        const float maxXVelocity = 300;
        ballXVelocity = CCRandom.GetRandomFloat (minXVelocity, maxXVelocity);

        score++;

        scoreLabel.Text = "Score: " + score;
    }
    // First let’s get the ball position:  
    float ballRight = ballSprite.BoundingBoxTransformedToParent.MaxX;
    float ballLeft = ballSprite.BoundingBoxTransformedToParent.MinX;
    // Then let’s get the screen edges
    float screenRight = VisibleBoundsWorldspace.MaxX;
    float screenLeft = VisibleBoundsWorldspace.MinX;
    // Check if the ball is either too far to the right or left:   
    bool shouldReflectXVelocity =
        (ballRight > screenRight && ballXVelocity > 0) ||
        (ballLeft < screenLeft && ballXVelocity < 0);
    if (shouldReflectXVelocity)
    {
        ballXVelocity *= -1;
    }
}

 

공이 움직이는 부분에대한 간단단 로직입니다. 등속도 운동이아닌 가속도 운동을 할겁니다. Random 때문에.

 

또, 각도에 관한 부분도 없기때문에 상식적으로 생각되는 방향과 다르게 나아갈수도 있습니다. 그리고 공이 paddle 아래에서 맞아도 위로 팅기는 신기한 현상을 볼 수 있습니다.

그러나, 튜토리얼 이기 때문에 내가 공튀기기 게임을 만들었다! 에 초점을 둡시다.

배포를 하거나 즐기기위해 만들려면 로직에 관한 부분을 알아서 수정하시면 됩니다.

(간단한 공튀기기만 봐도 간단한 물리상식이나 수학이 쓰이는게 보이네요.)

 

위 로직에대해 부연설명은

bool doesBallOverlapPaddle = ballSprite.BoundingBoxTransformedToParent.IntersectsRect(
paddleSprite.BoundingBoxTransformedToParent);

 

닿았나 안닿았나를 확인하는 부분이죠.

 

float ballRight = ballSprite.BoundingBoxTransformedToParent.MaxX;
float ballLeft = ballSprite.BoundingBoxTransformedToParent.MinX;

 

공의 오른쪽 왼쪽의 X좌표 값이 저장됩니다.

이것과 비슷하게 MaxY , MinY 로 paddle 아래에서 맞으면 튕기지 않게 설정할수 있습니다.

 

float screenRight = VisibleBoundsWorldspace.MaxX;
float screenLeft = VisibleBoundsWorldspace.MinX;

 

화면의 왼쪽 오른쪽 끝부분의 X좌표 값이 저장됩니다. 요부분을 이용해서 벽에 맞았을때 튕겨주게 합니다.

 

게임 로직은 다짰습니다. 근데 우리는 터치에관한 부분을 설정하지 않았죠!
공은 움직이나 paddle은 움직일수없는 상황입니다.

 

 

protected override void AddedToScene ()
{
    base.AddedToScene ();
    // Use the bounds to layout the positioning of our drawable assets
    CCRect bounds = VisibleBoundsWorldspace;
    // Register for touch events
    var touchListener = new CCEventListenerTouchAllAtOnce ();
    touchListener.OnTouchesEnded = OnTouchesEnded;
    // new code:
    touchListener.OnTouchesMoved = HandleTouchesMoved;
    AddEventListener (touchListener, this);
}

 

HandleTouchesMoved를 추가해놨습니다.

이제 HandleTouchesMoved 에 대해 작성해야죠.

 

void HandleTouchesMoved (System.Collections.Generic.List<CCTouch> touches, CCEvent touchEvent)
{
    // we only care about the first touch:
    var locationOnScreen = touches [0].Location;
    paddleSprite.PositionX = locationOnScreen.X;
}

 

이제 터치 부분까지 완성 되었습니다.

터치 부분도 오류가 있을겁니다. 위 코드는 터치가 된곳의 X좌표를 잡는건데 계속 막대를 끌고갈때는 괜찮지만

막대와 먼부분을 터치하면 그곳으로 막대가 순간이동 하게 되는 오류가 발생할 것입니다.

그런 오류가 있으면 안되겠죠.

게임로직 부분을 더 수정해서 한번 배포해보세요!

 

 

반응형

'C#' 카테고리의 다른 글

[C#] HSV에서 Hue 의 거리  (0) 2018.07.09
[C#] 이미지를 오로지 R , G , B 만으로 표현하기 (색 분류)  (0) 2017.12.28
[C#] 픽셀서치 PixelSearch 2  (0) 2017.09.16
[C#] 픽셀서치 PixelSearch  (4) 2017.08.25
[C#] 자마린 무료화  (0) 2017.03.01

댓글