반응형

프로퍼티 및 캡슐화(Property and Encapsulation)

캡슐화(Encapsulation)

객체 지향 프로그래밍의 주요 개념 중 하나이다. 데이터와 그 데이터를 조작하는 메서드를 하나로 묶어 “민감한” 데이터가 사용자에게 숨겨지도록 하는 것이다.

이전 포스트에서 접근 제한자의 사용 방법에 대해 다뤘다. private라는 접근 제한자를 사용함으로써 외부 클래스에서 접근을 제한했는데, 이와 같은 일련의 과정을 캡슐화(Encapsulation)라고 할 수 있을 것이다.

캡슐화는 다음과 같은 과정을 수행해야 한다.

  • 필드/변수를 private로 선언
  • private 필드에 액세스하고, 업데이트하기 위해 get, set 메소드를 사용

프로퍼티(Property)

앞서 설명한 거처럼 private는 동일한 클래스 내에서만 접근할 수 있다. 유니티로 개발을 하다 보면 때때로 외부 클래스에서 private 멤버에 접근해야 하는 경우가 있다. 이 것은 프로퍼티 통해 가능하다.

프로퍼티는 get, set 두 가지 메서드를 사용해 public으로 선언했을 때처럼 private 필드의 값을 읽고, 쓰고, 계산하는 멤버다. 멤버는 클래스 내에 있는 변수, 메서드 등을 멤버라고 한다.

예시 1:

class Person
{
  private string name; // 필드

  public string Name   // property
  {
    get { return name; }   // get 메소드
    set { name = value; }  // set 메소드
  }
}

Name 프로퍼티는 name 필드와 연결된다. 프로퍼티의 첫 글자는 대문자이고, 필드와 프로퍼티의 이름은 같게 하는 것이 권장된다.

get 메서드는 name 필드의 값을 반환한다. set 메소드는 name 필드에 값을 할당한다. 예시 1에서 value 키워드는 name 필드에 할당하는 값을 의미한다. 예시 2에서 조금 더 자세히 살펴보도록 하자.

예시 2:

class Person
{
  private string name; // 필드
  public string Name   // 프로퍼티
  {
    get { return name; }
    set { name = value; }
  }
}

class Program
{
  static void Main(string[] args)
  {
    Person myObj = new Person(); // 객체 생성
    myObj.Name = "Liam";
    Console.WriteLine(myObj.Name);
  }
}

//출력 : Liam

Program이라는 외부 클래스에서 Person 클래스에 대한 myObj라는 객체를 생성했다. 그리고 myObj객체에 Name 프로퍼티를 사용하여 name이라는 필드에 Liam이라는 값을 할당했다.

프로퍼티를 사용했기 때문에 private로 선언되었지만 외부 클래스에서 접근하여 필드의 값까지 수정할 수 있게 된 것이다.

자동 프로퍼티(Automatic Property)

C#에서는 단축/자동 프로퍼티를 사용하는 방법도 제공한다. 속성에 대한 필드를 정의할 필요가 없는 경우에는 예시 3에서 보는 것처럼 단축해서 코드를 작성할 수 있다.

예시 3:

class Person
{
  public string Name  // 프로퍼티
  { get; set; }
}

class Program
{
  static void Main(string[] args)
  {
    Person myObj = new Person();
    myObj.Name = "Liam";
    Console.WriteLine(myObj.Name);
  }
}

//출력 Liam

예시 2예시 3은 기능적으로는 동일하다고 불 수 있다. 그러나 내부적으로는 다르게 구현되어 있다. 예시 2에서는 name필드에 Liam이라는 값을 할당하지만, 예시 3에서는 백킹 필드(Backing Filed)라는 비공개 변수에 값이 할당된다.

백킹 필드는 프로퍼티에 의해 읽거나 쓰이는 실제 데이터를 저장하는 데 사용되는 비공개 변수다. 프로퍼티의 내부에서만 사용되며, 클래스 외부에서는 백킹 필드로 직접적으로 접근할 수는 없다.

그렇다면 여러 객체를 생성하는 경우에는 어떻게 될까? 당연하게도 객체마다 프로퍼티를 사용했을 때 각각의 백킹 필드가 할당된다.

부가적으로 설명하자면 객체를 생성하면 컴퓨터 메모리에 객체의 데이터와 메서드를 저장하기 위한 메모리 블록이 할당된다. 이 메모리 블록 내에서 객체와 관련된 모든 멤버에 대한 메모리가 할당되는 것이고, 고유 주소를 갖게 된다. 백킹 필드 객체가 할당받은 메모리 블록 내에 데이터를 저장하는 것이다.

총정리

캡슐화와 프로퍼티는 객체 지향 프로그래밍에서 코드의 가독성, 유지 보수성, 재사용성, 보안 등을 향상하는데 중요한 역할을 한다.

캡슐화로 외부에서 민감한 데이터에 접근하는 것을 막음과 동시에, 개발자가 프로퍼티를 통해 접근할 수 있게 한다.

반응형
반응형

접근 제한자(Access Modifier)란 무엇인가?

우리나라에서는 접근 제한자 또는 접근 한정자라고 부르는 거 같다. 정리해 보자면 접근 제한자는 클래스, 필드, 메소드 및 속성에 대한 접근 수준을 설정하는 데 사용된다. 아래 예시를 살펴보자.

예시1:

public string color;

예시1에서 public을 접근 제한자라고 한다. 가장 앞

C#의 접근 제한자

접근 제한자 설명
public 외부 클래스를 포함한 모든 클래스에서 접근을 허용함.
private 동일한 클래스 내에서만 접근을 허용함.
protected 동일한 클래스 내에서 또는 해당 클래스에서 상속된 클래스에서 접근을 허용함.
internal 자체 어셈블리 내에서만 접근을 허용함. 다른 어셈블리에서는 없음.

 

private protectedprotected internal처럼 조합하여 사용하는 경우도 있다. protectedinternal에 대해서는 나도 사용해 본 적이 없다. 추후에 다루게 된다면 다시 한번 정리해 보도록 하겠다.

Private Modifier(비공개 제한자)

private를 사용하여 필드를 선언하면 동일한 클래스 내에서만 접근할 수 있다.

예시2:

class Car
{
        private string model = "Mustang";

      static void Main(string[] args)
    {
            Car myObj = new Car();
        Console.WriteLine(myObj.model);
    }
}

// 출력 : Mustang

예시3:

class Car
{
  private string model = "Mustang";
}

class Program
{
  static void Main(string[] args)
  {
    Car myObj = new Car();
    Console.WriteLine(myObj.model);
  }
}

model 변수는 Car 클래스 내부에서 private로 선언되었다. 그런데 Program 클래스에서 접근을 하려고 하는 경우 다음과 같은 에러가 발생한다.

private를 사용했기 때문에 외부 클래스에서 접근할 수 없다.

접근 제한자를 사용하여 클래스와 멤버의 접근을 제어함으로써 코드의 안정성을 높일 수 있다.

반응형
반응형

클래스 생성자 🛠️

via GIPHY

 

생성자는 객체를 초기화하는 데 사용되는 특수 메서드이다. 생성자의 장점은 클래스의 객체가 생성될 때 호출된다는 것이다. 필드의 초기 값을 설정하는 데 사용할 수 있다.

예시1:

// Car 클래스 생성
class Car
{
  public string model;  // 필드 생성(클래스 내의 변수 == 필드)

  // Car 클래스에 대한 클래스 생성자 생성
  public Car()
  {
    model = "Mustang"; // model의 초기값 설
  }

  static void Main(string[] args)
  {
    Car Ford = new Car();  // Car 클래스의 객체를 생성(생성자를 호출)
    Console.WriteLine(Ford.model); 
  }
}

// 출 "Mustang"

1. 생성자를 생성할 때 알아두면 좋은 점

  1. 생성자 이름은 클래스 이름과 일치해야한다.
  2. 클래스 생성자는 반환 유형(예: void 또는 int)을 가질 수 없다.
  3. 객체가 생성될 때 생성자가 호출된다.
  4. C#에서는 클래스 생성자를 직접 만들지 않으면 자동으로 생성함. 그러나 초기값 설정을 할 수 없음.

2. 생성자 매개변수(Constructor Parameters)

생성자는 필드를 초기화하는 데 사용되는 매개변수를 사용할 수도 있다.

예시2:

class Car
{
  public string model;

  // 매개변수와 함께 Car클래스에 대한 생성자 생성
  public Car(string modelName)
  {
    model = modelName;
  }

  static void Main(string[] args)
  {
    Car Ford = new Car("Mustang");
    Console.WriteLine(Ford.model);
  }
}

// 출력 "Mustang"

예시2 를 보면 매개변수를 추가해서 Ford라는 객체를 생성하면서 초기값을 지정한다. 예시3 을 조금 더 살펴보자.

예시3:

class Car
{
  public string model;
  public string color;
  public int year;

  // 여러가지 매개변수를 추가
  public Car(string modelName, string modelColor, int modelYear)
  {
    model = modelName;
    color = modelColor;
    year = modelYear;
  }

  static void Main(string[] args)
  {
    Car Ford = new Car("Mustang", "Red", 1969);
    Console.WriteLine(Ford.color + " " + Ford.year + " " + Ford.model);
  }
}

// 출력: Red 1969 Mustang

Ford뿐만 아니라 Hyundai, KIA라는 객체를 생성한다고 했을 때, 매개변수의 값만 바꿔주면 각 객체마다 설정을 해줄 수 있다. 예시4와 예시5를 살펴보자. 생성자는 코드를 간결하게 바꿔주는데 도움을 준다.

예시4:

//생성자를 사용하지 않았을 때

class Car
{
    public string model;
    public string color;
    public int year;

  static void Main(string[] args)
  {
    Car Ford = new Car();
    Ford.model = "Mustang";
    Ford.color = "red";
    Ford.year = 1969;

    Car Hyundai = new Car();
    Hyundai.model = "Avante";
    Hyundai.color = "white";
    Hyundai.year = 2005;

    Car Kia = new Car();
    Kia.model = "Ray";
    Kia.color = "blue";
    Kia.year = 2017;

    Console.WriteLine(Ford.model);
    Console.WriteLine(Hyundai.model);
    Console.WriteLine(Kia.model);
  }
}

예시5:

//생성자를 사용했을 때

class Car
{
    public string model;
    public string color;
    public int year;

    public Car(string modelName, string modelColor, int modelYear)
  {
    model = modelName;
    color = modelColor;
    year = modelYear;
  }

  static void Main(string[] args)
  {
    Car Ford = new Car("Mustang", "Red", 1969);
    Car Hyundai = new Car("Avante", "White", 2005);
    Car Kia = new Car("Ray", "blue", 2017);

    Console.WriteLine(Ford.model);
    Console.WriteLine(Hyundai.model);
    Console.WriteLine(Kia.model);
  }
}

나같은 초보자가 얼핏보기엔 코드의 길이면에서는 큰 차이가 없어보인다고 생각할지도 모르겠다. 하지만 생성자의 사용은 다음과 같은 장점을 가진다.

  1. 코드의 간결성 :

객체를 인스턴스화할 때 필요한 모든 초기화 작업을 한 곳에서 처리할 수 있다. 예시4에서는 3개의 객체를 생성하는데 15줄이 사용됐다. 객체를 30개 이상 생성한다면 코드의 양은 150줄이 될 것이다. 생성자를 사용한다면 30줄이다.

 

   2. 유지보수성 :

 

예를 들어 Car 클래스의 model이라는 필드의 이름을 수정할 일이 생겼다고 가정해보자. 예시4경우라면 코드를 하나하나 수정해야한다. 하지만 예시5의 경우에는 생성자 내부의 코드만 수정해주면 된다.

 

생활코딩 강의를 들었을 때 개발을 하면서 ‘극단적인 가정’을 해보면 효율성을 개선하는데 도움이 된다는 내용이 있었다. 1,2개 였을 때는 잘 안 보이던 비효율이, 5천개라면? 1만개라면? 이라는 가정에서는 명백하게 드러나는 경우가 있다.

반응형
반응형

객체지향이란 무엇일까?

tenor

객체 지향 프로그래밍(Object-Oriented Programming)은 마치 레고 블록을 조립하듯이, 프로그래밍을 하는 방식이다. 레고 블록처럼, 객체지향 프로그래밍에서는 **클래스(Class)**라는 틀을 만들고, 그 틀을 사용해서 **객체(Object)**라는 작은 부품들을 만든다. 이렇게 만들어진 객체들은 각자의 정보(데이터)와 할 수 있는 일(메소드)를 가지고 있다.

  • 클래스는 레시피: 클래스는 마치 요리 레시피와 같다. 레시피대로 음식을 만들면, 똑같은 맛의 음식을 여러 번 만들 수 있는 거처럼, 클래스도 재사용이 가능하다.
  • 객체는 요리된 음식: 객체는 레시피대로 만들어진 음식이다. 각각의 객체는 독립적으로 존재하면서, 각자의 특성을 가지고 있다.
  • 코드 재사용: 객체지향 프로그래밍은 레고 블록을 여러 번 재사용할 수 있듯이, 코드를 재사용할 수 있게 해줘서, 더 적은 코드로 더 많은 일을 할 수 있다.
  • 유지보수 용이: 레고 블록을 쉽게 분리하고 다시 조립할 수 있는거처럼, 객체지향 프로그래밍 역시 코드를 쉽게 변경하고 관리하기가 좋다.

이렇게 객체지향 프로그래밍은 복잡한 프로그램도 깔끔하고 체계적으로 만들 수 있도록 돕는다. 마치 레고 블록으로 큰 성을 만들 듯이, 작은 부품들을 조합해서 멋진 프로그램을 만들 수 있다.

절차적 프로그래밍과의 차이는?

앞서 비유한 바와 같이 요리와 레고에 빗대어 객체 지향 프로그래밍(OOP)과 절차적 프로그래밍의 차이를 정리해본다.

  • 객체 지향 프로그래밍 (OOP):
    • 장난감 상자: 상자 안에 여러 가지 장난감이 있다고 하자. 각각의 장난감은 이름과 색깔, 기능을 가지고 있다. 예를 들어, '레고 블록’은 쌓을 수 있고, '자동차 장난감’은 굴러간다. 이 장난감들은 각자의 역할을 가지고 있어서, 우리가 상상하는 세계를 만들 수 있다.
    • 역할 분담: 장난감들을 활용해 여러 가지 놀이를 할 수 있다. 레고 블록으로 집을 지으면, 자동차 장난감은 그 집 앞을 지나가고, 인형은 그 집에서 사는 거처럼 말이다.
  • 절차적 프로그래밍:
    • 요리 레시피: 요리할 때 레시피를 따라 하나씩 재료를 준비하고, 조리 순서대로 음식을 만든다. 모든 단계가 순서대로 진행된다.
    • 단계별 작업: 먼저 쌀을 씻고, 물을 붓고, 밥을 짓는 것처럼, 모든 작업이 차례대로 이루어진다. 각 단계는 전체 요리 과정의 일부분이다.

 

이렇게 객체지향 프로그래밍은 여러 가지 재료를 만들고, 그 재료를 조합해 프로그램을 만들어 가는 방식이다. 장난감 상자 안의 장난감들이 각자의 역할을 가지고 놀이의 재료가 되는 거처럼말이다.

 

여기서 내가 만든 놀이를 프로그램이라고 할 수 있을 것이고, 장난감 상자를 클래스(Class), 장난감들을 객체(Object)라고 볼 수 있다. 클래스와 객체에 대해서는 추후에 다시 한 번 정리하도록 하겠다.

 

반면, 절차적 프로그래밍은 요리 레시피를 따라 순서대로 작업을 수행하는 것과 비슷하다. 이름에서 직관적으로 알 수 있듯이 단계적으로 진행되는 것이 절차적 프로그래밍의 특징이다.

 

C# == 객체지향 프로그래밍 언어

C#은 Microsoft가 개발한 객체 지향 프로그래밍 언어이다. 유니티로 게임 개발을 할 때 스크립팅 언어로 사용된다. 이 외에도 Windows 플랫폼에서 개발된 소프트웨어를 위해 사용됩니다. 국내보다는 해외에서 더 많이 사용된다고 한다.

 

유니티는 왜 c#을 채택했나?

게임 개발에 자주 사용되는 C++와 비교할 때, 한 가지 주요한 이유는 배우기 쉽고 직관적인 구조를 가진다는 점이다. 유니티의 슬로건은 '게임 개발의 민주화'로, 초보자부터 전문가까지 모두에게 적합한 선택지를 제공한다.

ps. 유니티 엔진은 C++로 개발되었다.

 

이 외에도 여러가지 이유가 있지만 성능면에서 효율적인 면에서 C#이 좋은 평가를 받고 있기 때문이라는 의견도 있다.


 

반응형
반응형

클래스와 객체

C#의 모든 것은 해당 속성 및 메서드와 함께 클래스 및 객체와 연결된다.

자동차를 예를 들어보자. 자동차는 클래스와 같다. 무게, 색상, 브랜드 등과 같은 것을 속성, 구동 및 브레이크와 같은 것들을 메서드라고 비유할 수 있다. 폭스바겐 자동차를 만들고 싶다면 자동차 클래스의 속성과 메서드를 사용하여 만들 수 있다. 이때 만들어진 폭스바겐 자동차가 객체다.

다음은 필수는 아니지만 알아두면 도움 될만한 내용이다.

  1. C# 파일과 클래스 이름을 동일하게 가져가는 경우가 많다. 코드를 체계적으로 정리하기 위함이다.
  2. 클래스 이름을 지정할 때 첫 글자는 대문자로 한다.
  3. 변수가 클래스 내에서 직접 선언되면 이를 ‘필드(field)’ 또는 ‘속성(attribute)’라고 한다.

객체 만들기

예시:

class Car // 자동차 클래스
{
  string color = "red"; // 필드

  static void Main(string[] args)
  {
    Car myObj = new Car(); // myObj라는 객체 생성
    Console.WriteLine(myObj.color);
  }
}

클래스(myObj.color) 내의 변수/필드에 접근하려면 점 구문(.)을 사용한다.

다중 객체

예시:

class Car
{
  string color = "red";
  static void Main(string[] args)
  {
    Car myObj1 = new Car();
    Car myObj2 = new Car();
    Console.WriteLine(myObj1.color);
    Console.WriteLine(myObj2.color);
  }
}

한 클래스에 여러 객체를 만드는 것도 가능하다.

여러 클래스 사용1

클래스의 객체를 생성하고 다른 클래스에서 접근할 수도 있다. 이는 더 나은 클래스 구성을 위해 자주 사용된다고 한다.

Car.cs

class Car
{
  public string color = "red"; // 접근제한자 public 사용
}

Program.cs

class Program
{
  static void Main(string[] args)
  {
    Car myObj = new Car();
    Console.WriteLine(myObj.color);
  }
}

Car.cs스크립트를 살펴보면 string앞에 public이라는 키워드가 사용되었다. 이 키워드는 다른 클래스에서의 접근을 허락한다. 이를 접근 제한자 또는 접근 한정자라고 부르는데, public이 외에도 private, protected 등이 있다.

클래스 멤버

앞서 클래스 내에서 선언된 변수를 필드 또는 속성이라고 부른다고 하였다. 클래스 멤버는 클래스 내에 있는 필드 또는 메서드들을 뜻하는 말이다.

예시:

class MyClass
{
  // 클래스 멤버
  string color = "red";        // 필드
  int maxSpeed = 200;          // 필드
  public void fullThrottle()   // 메소드
  {
    Console.WriteLine("The car is going as fast as it can!");
  }
}

필드를 비워 두고 객체를 생성할 때 수정할 수도 있다. 이는 한 클래스에서 여러 객체를 사용할 때 특히 유용하다.

예시:

class Car // Car클래스
{
    // 클래스 멤버
  string model; // 비어 있는 상태로 선언된 필드
  string color;
  int year;

  static void Main(string[] args)
  {
    Car Ford = new Car(); // Car클래스에서 Ford 객체 생성
    Ford.model = "Mustang"; 
    Ford.color = "red";
    Ford.year = 1969;

    Car Opel = new Car(); // Car클래스에서 Opel 객체 생성
    Opel.model = "Astra";
    Opel.color = "white";
    Opel.year = 2005;

    Console.WriteLine(Ford.model);
    Console.WriteLine(Opel.model);
  }
}

프로그램에서 사용되는 데이터는 실행될 때 컴퓨터 메모리(RAM)에 저장 공간을 할당받는다. 클래스에 객체가 만들어질 때도 각각의 객체마다 별도로 저장 공간을 할당 받는다.

즉, Ford.model = “Mustang”;이라는 코드가 실행되면 Ford라는 객체의 modelMustang이라는 값이 저장되는 것이지 Car 클래스의 model 필드에 Mustang이라는 값이 저장되지 않는다.

ps. 단 클래스를 정의하는 것만으로는 저장공간이 할당되지 않는다고 한다.

객체 메서드

메서드는 일반적으로 클래스에 속하며, 객체의 동작 방식을 정의한다. 필드와 마찬가지로 점 구문(.)을 사용하여 메서드에 접근 할 수 있다. 그리고 메서드를 호출할 때는 이름 뒤에 두 개의 괄호()와 세미콜론을 사용한다.

예시:

class Car
{
  string color;                 // field
  int maxSpeed;                 // field
  public void fullThrottle()    // method, public 접근 제한자를 사용했다는 점에 유의하자.
  {
    Console.WriteLine("The car is going as fast as it can!");
  }

  static void Main(string[] args)
  {
    Car myObj = new Car();
    myObj.fullThrottle();  // Call the method
  }
}

앞의 예시에서 fullThrottle 메소드 앞에 public 키워드가 붙었다. 그 이유는 public(공개) 메소드는 객체를 생성한 후에만 호출이 가능하고, 객체의 상태를 변경하거나 기능을 실행할 수 있다.

ps.static(정적) 메서드의 경우에는 클래스의 객체를 만들지 않고도 접근할 수 있지만, 클래스의 필드에 직접 접근하여 값을 수정할 수 없다.

반응형
반응형

반복문(Loop)

While문

루프는 지정된 조건에 도달하는 한 코드 블록을 실행할 수 있다.

지정된 조건이 True인 동안 코드블록을 반복 실행한다.

 

주의 할 점은 조건 안의 변수값을 증가시켜야 한다.

그렇지 않으면 무한 루프에 빠진다.

 

예시:

int i = 0;
while (i < 5)
{
  Console.WriteLine(i);
  i++;
}

Do/While문

do/while 루프는 while 루프의 변형이다.

이 루프는 조건이 true인지 확인하기 전에 코드 블록을 한 번 실행한 다음 조건이 true인 동안 루프를 반복한다.

조건이 거짓이더라도 코드가 한 번은 실행된다.

 

예시:

int i = 0;
do
{
  Console.WriteLine(i);
  i++;
}
while (i < 5);

For문

코드 블록을 반복하려는 횟수를 정확히 알고 있으면 while 루프 대신 for 루프를 사용한다.

예시:

for (statement 1; statement 2; statement 3)
{
// code block to be executed}

Statement 1 코드 블록이 실행되기 전에 (한 번) 실행됩니다.

Statement 2 코드 블록 실행 조건을 정의합니다.

Statement 3 코드 블록이 실행된 후에 (매번) 실행됩니다.

  • 중첩루프

루프를 다른 루프 안에 배치하는 것도 가능하다.

이것을 중첩 루프라고 한다.

"내부 루프"는 "외부 루프"가 반복될 때마다 한 번씩 실행된다.

 

예시:

// Outer loop
for (int i = 1; i <= 2; ++i)
{
  Console.WriteLine("Outer: " + i);  // Executes 2 times

  // Inner loop
  for (int j = 1; j <= 3; j++)
  {
    Console.WriteLine(" Inner: " + j); // Executes 6 times (2 * 3)
  }
}

// Outer: 1
//  Inner: 1
//  Inner: 2
//  Inner: 3
// Outer: 2
//  Inner: 1
//  Inner: 2
//  Inner: 3

Foreach문

배열의 요소를 반복하는 데만 사용되는 foreach 루프도 있다.

예시:

foreach (type variableName in arrayName)
{
// code block to be executed}
}

// 구체적인 예시

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
foreach (string i in cars) 
{
  Console.WriteLine(i);
}

// Volvo
// BMW
// Ford
// Mazda

Break / Continue

  1. Break : switch문에서 사용되기도 하고, 반복문에서 벗어날 때 사용하기도 한다.
  2. Continue : 지정된 조건이 발생할 경우 반복을 건너뛰고, 다음 루프로 넘어간다.

Break문 예시:

int i = 0;
while (i < 10)
{
  Console.WriteLine(i);
  i++;
  if (i == 4)
  {
    break;
  }
}

// 0
// 1
// 2
// 3

Continue문 예시:

int i = 0;
while (i < 10)
{
  if (i == 4)
  {
    i++;
    continue;
  }
  Console.WriteLine(i);
  i++;
}

// 0
// 1
// 2
// 3
// 5
// 6
// 7
// 8
// 9

배열 (Array)

배열은 각 값에 대해 별도의 변수를 선언하는 대신 단일 변수타입에 여러 값을 저장하는 데 사용

// 빈 배열 선언

string[] cars;

// 값이 삽입된 배열 선언

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"}; // 문자열
int[] myNum = {10, 20, 30, 40}; // 정수

인덱스 번호를 참조하여 배열 요소에 액세스 할 수 있다.

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
Console.WriteLine(cars[0]);

// Volvo

배열 요소 바꾸기:

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
cars[0] = "Opel";
Console.WriteLine(cars[0]);

// Opel

배열 길이 구하기:

string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
Console.WriteLine(cars.Length);

// 4

배열을 선언하는 여러가지 방법:

// 배열의 크기만 생성
string[] cars = new string[4];

// 배열의 크기와 함께 값을 할당
string[] cars = new string[4] {"Volvo", "BMW", "Ford", "Mazda"};

// 배열의 크기는 지정하지 않고, 값만 할당
string[] cars = new string[] {"Volvo", "BMW", "Ford", "Mazda"};

// 배열의 크기는 지정하지 않고, 값만 할당
string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};

배열을 선언하고 나중에 초기화하는 경우 new 키워드를 사용해야 한다.

배열을 초기화한다는 것은 배열에 처음으로 값을 할당하는 과정을 의미한다.

new 키워드를 사용하는 경우 기존의 배열은 사라지고, 새로운 배열이 할당된다.

배열의 정렬

배열은 알파벳순이나 오름차순으로 정렬하는 Sort()와 같은 다양한 배열 메서드를 사용하여 정렬할 수 있다.

  1. Sort()
// Sort a string
string[] cars = {"Volvo", "BMW", "Ford", "Mazda"};
Array.Sort(cars);
foreach (string i in cars)
{
  Console.WriteLine(i);
}

// Sort an int
int[] myNumbers = {5, 1, 8, 9};
Array.Sort(myNumbers);
foreach (int i in myNumbers)
{
  Console.WriteLine(i);
}
  1. System.Linq

Min, Max 및 Sum과 같은 다른 유용한 배열 메서드는 System.Linq 네임스페이스에 있다.

using System;
using System.Linq;

namespace MyApplication
{
  class Program
  {
    static void Main(string[] args)
    {
      int[] myNumbers = {5, 1, 8, 9};
      Console.WriteLine(myNumbers.Max());  // returns the largest value
      Console.WriteLine(myNumbers.Min());  // returns the smallest value
      Console.WriteLine(myNumbers.Sum());  // returns the sum of elements
    }
  }
}

다차원 배열

  1. 2차원 배열

2D 배열을 만들려면 자체 중괄호 세트 내에 각 배열을 추가하고 대괄호 안에 쉼표(,)를 삽입한다.

예시:

int[,] numbers = { {1, 4, 2}, {3, 6, 8} };

단일 쉼표 [,]는 배열이 2차원임을 지정한다.

3차원 배열에는 int[,,]라는 두 개의 쉼표가 있다.

배열을 행과 열이 있는 테이블로 생각하면 쉽다.

출처 : w3school

  1. 2차원 배열에 엑세스하는 방법

예시:

int[,] numbers = { {1, 4, 2}, {3, 6, 8} };
Console.WriteLine(numbers[0, 2]);  // Outputs 2

배열 인덱스는 0부터 시작한다. [0]이 첫 번째 요소이다.

 

배열의 요소 변경:

int[,] numbers = { {1, 4, 2}, {3, 6, 8} };
numbers[0, 0] = 5;  // Change value to 5
Console.WriteLine(numbers[0, 0]); // Outputs 5 instead of 1

2차원 배열의 루프:

// Using foreach

int[,] numbers = { {1, 4, 2}, {3, 6, 8} };

foreach (int i in numbers)
{
  Console.WriteLine(i);
}

// Using for
int[,] numbers = { {1, 4, 2}, {3, 6, 8} };

for (int i = 0; i < numbers.GetLength(0); i++) 
{ 
  for (int j = 0; j < numbers.GetLength(1); j++) 
  { 
    Console.WriteLine(numbers[i, j]); 
  } 
}  

메소드

메소드는 호출될 때만 실행되는 코드 블록이다.

매개변수라고 하는 데이터를 메서드에 전달할 수 있다.

메소드는 특정 작업을 수행하는 데 사용되며 함수라고도 한다.

 

메소드를 사용하는 이유는 코드의 가독성을 높이고,

반복되는 코드를 재사용하기 위함이다.

 

C#에서는 메서드 이름을 대문자로 시작하는 것이 권장된다.

예시:

class Program // 클래스 이름
{
  static void MyMethod() 
  {
    // code to be executed
  }
}
반응형
반응형

C# 문자열

문자열은 일련의 문자로 구성되며, 각 문자는 0부터 시작하는 인덱스 값으로 참조할 수 있다.

예를 들어, "Hello"라는 문자열이 있다면 'H'는 인덱스 0, 'e'는 인덱스 1과 같은 방식이다.

 

또한, C#에서는 IndexOfSubstring이라는 두 가지 유용한 문자열 메소드를 제공한다.

IndexOf 메소드는 특정 문자나 문자열이 처음으로 출현하는 인덱스를 반환한다.

만약 해당 문자나 문자열이 존재하지 않는다면 -1을 반환한다. 아래 예시를 살펴보자.

 

예시:

string str = "Hello World";
int index = str.IndexOf('W'); // index는 6이 됩니다.

Substring 메소드는 문자열의 특정 부분을 추출하는 데 사용된다.

이 메소드는 두 가지 형태가 있다. 하나는 시작 인덱스만 지정하는 것이고,

다른 하나는 시작 인덱스와 길이를 모두 지정하는 것이다.

 

예시:

string str = "Hello World";
string sub = str.Substring(6); // sub는 "World"가 된다.
string sub2 = str.Substring(0, 5); // sub2는 "Hello"가 된다.

이러한 메소드들은 문자열을 다루는 데 있어 매우 중요하므로 잘 기억해두는 것이 좋다.

C# If 문

C#에서 if문은 주어진 조건이 참인 경우에만 코드 블록을 실행한다.
그런데 여러 조건을 확인해야 할 경우에는 if, else if, else 구조를 사용한다.


이 구조에서 if 문 다음에 오는 else if 문은 이전의 ifelse if 문의 조건이 거짓일 때만 확인된다.
그리고 else 문은 앞선 모든 조건이 거짓일 경우에 실행된다.

if (condition1)
{
    // 조건1이 참일 때 실행
}
else if (condition2)
{
    // 조건1이 거짓이고, 조건2가 참일 때 실행
}
else
{
    // 모든 조건이 거짓일 때 실행
}

또한 C#에서는 삼항 연산자를 사용하여 if-else 문을 간단하게 표현할 수 있다.
삼항 연산자는 "조건 ? 값1 : 값2"와 같은 형태로, 조건이 참일 경우 값1을, 거짓일 경우 값2를 반환한다.

var result = (condition) ? "참일 때의 값" : "거짓일 때의 값";

C# Switch문 정리

  • switch 문의 작동 원리: 주어진 조건에 따라 여러 코드 블록 중 하나를 실행.
  • break 키워드: switch 블록에서 더 이상의 코드 실행이나 케이스 테스트를 중단.
  • default 키워드: 모든 케이스와 일치하지 않을 때 실행될 코드를 지정.

예시코드

int day = 4; // 예를 들어, 오늘은 목요일
switch (day)
{
    case 1:
        Console.WriteLine("Monday");
        break;
    case 2:
        Console.WriteLine("Tuesday");
        break;
    case 3:
        Console.WriteLine("Wednesday");
        break;
    case 4:
        Console.WriteLine("Thursday"); // day 4에 해당하므로 "Thursday"가 출력.
        break;
    case 5:
        Console.WriteLine("Friday");
        break;
    case 6:
        Console.WriteLine("Saturday");
        break;
    case 7:
        Console.WriteLine("Sunday");
        break;
    default:
        Console.WriteLine("Looking forward to the Weekend."); // 일치하는 케이스가 없을 때 출력.
        break;
}
반응형
반응형

C# 타입 캐스팅

C#에는 두 가지 유형의 타입 캐스팅이 있다. 암시적 캐스팅과 명시적 캐스팅이다.

 

Implicit Casting(암시적 캐스팅) 

더 작은 크기의 유형을 더 큰 크기의 유형으로 전달할 때 암시적 캐스팅이 자동으로 수행한다.Automatic casting이라고도 한다.

예시:

int myInt = 9;
double myDouble = myInt;       // Automatic casting: int to double

Console.WriteLine(myInt);      // Outputs 9
Console.WriteLine(myDouble);   // Outputs 9

Explicit Casting(명시적 캐스팅)

명시적 캐스팅은 값 앞에 괄호 안에 유형을 배치하여 수동으로 수행한다.Manual casting이라고도 한다.

예시:

double myDouble = 9.78;
int myInt = (int) myDouble;    // Manual casting: double to int

Console.WriteLine(myDouble);   // Outputs 9.78
Console.WriteLine(myInt);      // Outputs 9

Type Conversion Methods(유형 변환 방법)

Convert.ToBoolean, Convert.ToDouble, Convert.ToString, Convert.ToInt32(int)Convert.ToInt64(long)와 같은 내장 메서드를 사용하여 명시적으로 데이터 형식을 변환한다.

예시:

int myInt = 10;
double myDouble = 5.25;
bool myBool = true;

Console.WriteLine(Convert.ToString(myInt));    // convert int to string
Console.WriteLine(Convert.ToDouble(myInt));    // convert int to double
Console.WriteLine(Convert.ToInt32(myDouble));  // convert double to int
Console.WriteLine(Convert.ToString(myBool));   // convert bool to string
  • 변환이 왜 필요한가? : 유형 변환이 필요한 경우는 많지 않다고 한다. 사용자 입력 등에서 작업할 때 사용되기도 한다. 아래 예시를 살펴보자.
Console.WriteLine("Enter your age:");
int age = Convert.ToInt32(Console.ReadLine()); // string으로 입력받은 값을 Int로 변환 후 age에 할당
Console.WriteLine("Your age is: " + age);

매개변수

Parameter(매개변수) & Arguments(인수)

  1. Parameter(매개변수) : 매개변수는 메소드 내에서 변수 역할을 한다. 원하는 만큼 추가 할 수 있고, 쉼표로 구분한다.
  2. Arguments(인수) : 매개변수가 메소드에 전달되면 이를 인수라고 한다.

예시:

static void MyMethod(string fname) // fname == Parameters
{
  Console.WriteLine(fname + " Refsnes");
}

static void Main(string[] args)
{
  MyMethod("Liam"); // Liam and 2 others == Arguments
  MyMethod("Jenny");
  MyMethod("Anja");
}

Multiple Parameters(다중 매개변수)

static void MyMethod(string fname, int age) // 두 개의 매개변수
{
  Console.WriteLine(fname + " is " + age);
}

static void Main(string[] args)
{
  MyMethod("Liam", 5);
  MyMethod("Jenny", 8);
  MyMethod("Anja", 31);
}

Default Parameter Value(기본 매개변수 값)

등호(=)를 사용하여 기본 매개변수 값을 사용한다.

static void MyMethod(string country = "Norway")
{
  Console.WriteLine(country);
}

static void Main(string[] args)
{
  MyMethod("Sweden");
  MyMethod("India");
  MyMethod(); // 매개변수를 지정해주지 않았으므로 Norway가 출력된다.
  MyMethod("USA");
}

// Sweden
// India
// Norway
// USA

Return Values(반환값)

메소드가 값을 반환하도록 하려면 void 대신 기본 데이터 유형(예: int 또는 double)을 사용하고 메소드 내부에서 return 키워드를 사용한다.

static int MyMethod(int x)
{
  return 5 + x;
}

static void Main(string[] args)
{
  Console.WriteLine(MyMethod(3));
}

// Outputs 8 (5 + 3)

반환값을 변수에 저장 하는 것도 가능하다.

static int MyMethod(int x, int y)
{
  return x + y;
}

static void Main(string[] args)
{
  int z = MyMethod(5, 3);
  Console.WriteLine(z);
}

// Outputs 8 (5 + 3)

Named Arguments

key: value 구문을 사용하여 인수를 보내는 것도 가능하다.

static void MyMethod(string child1, string child2, string child3)
{
  Console.WriteLine("The youngest child is: " + child3);
}

static void Main(string[] args)
{
  MyMethod(child3: "John", child1: "Liam", child2: "Liam");
}

// The youngest child is: John

Method Overloading

메소드 오버로딩을 사용하면 여러 메소드가 서로 다른 매개변수를 사용하여 동일한 이름을 가질 수 있다.

static int PlusMethod(int x, int y)
{
  return x + y;
}

static double PlusMethod(double x, double y)
{
  return x + y;
}

static void Main(string[] args)
{
  int myNum1 = PlusMethod(8, 5);
  double myNum2 = PlusMethod(4.3, 6.26);
  Console.WriteLine("Int: " + myNum1);
  Console.WriteLine("Double: " + myNum2);
}
반응형
반응형

플레이어의 체력을 관리하는 스크립트 'PlayerHealthController'

플레이어의 체력과 관련된 기능을 관리하는 PlayerHealthController라는 스크립트에 무적 상태를 구현했습니다.

먼저 float 타입의 변수 invincibleLength와 invincibleCounter를 선언했습니다.

invincibleLength에는 무적 상태가 유지되는 시간을 할당하고,

invincibleCounter에는 무적 상태가 유지된 시간을 관리할 것입니다.

 

자세히 보시면 invincibleLength의 경우 public으로 선언되어 있고

invincibleCounter는 private으로 선언되어 있습니다.

invincibleLength 변수는 인스펙터창에서 값을 설정할 수 있어야 하기 때문에 public으로 선언하였습니다.

DealDamge함수 내에서 무적 상태가 유지되는 시간(invincibleLength)를

무적 상태가 유지되는 시간(invincibleCounter)에 할당했습니다.

저는 유니티 에디터에서 invincibleLength에 1이라는 값을 할당해둔 상태입니다.

즉 무적 상태가 유지되는 시간은 1초입니다.

 

왜 1초인가 하면

프레임마다 호출되는 Update 함수 내에서

invincibleCounter값에서 Time.deltaTime만큼의 시간을 빼주고 있기 때문입니다.

 

Time.deltaTime은 이전 프레임과 현재 프레임 사이의 시간 간격을 나타냅니다.

만약 플레이 환경이 초당 프레임이 60이라면(1초에 60번 화면이 업데이트)

1프레임과 1프레임 사이의 간격은 1초를 60으로 나눈 값이 될 것입니다.

 

즉, invincibleCounter에 할당된 1이라는 값(invincibleLength에 할당된 값)에서

1초를 60으로 나눈 값을 매 프레임마다 빼는 것입니다.

정리해보겠습니다.

 

1. 60FPS = 1초에 화면이 60번 업데이트 된다는 뜻

2. 1프레임 마다 1/60초를 1초에서 뺌.

3. 그럼 2번 과정이 1초에 60번 실행됨. 

4. 1초가 지나면 invincibleCounter의 값은 0에 수렴함.

 

invincibleCounter값이 0보다 크다면 프레임마다 1/60초를 빼줍니다.

invincibleCounter값이 0이하인 경우 1이라는 값(invincibleLength)을 넣어 줌으로써

캐릭터의 체력이 떨어지는 함수인 DealDamage가 실행되지 않습니다.

반응형
반응형

유니티에서 배경을 활용하는 방법은 매우 다양할 것입니다.

저는 오늘 2D플랫포머 게임을 만드는 과정에서

MainCamera 오브젝트와 스크립트를 사용하여,

배경을 재활용하는 방법에 대해 정리해보고자 합니다.

 

만드는 게임의 배경과 오브젝트들

제가 따라 만들고 있는 게임에서 배경은 2가지입니다.

우측 하이어라키창에 Background의 자식 오브젝트인 Far와 Middle입니다.

Far는 좌측 사진에서 빨간 네모 안의 하늘이미지, Middle은 빨간 네모 안의 수풀 이미지입니다.

 

CameraController라는 스크립트를 보면서

카메라와 배경이 플레이어를 따라다니도록 구현하는 코드들을 살펴보겠습니다.

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

public class CameraController : MonoBehaviour
{

    public Transform Player;
    public Transform farBackground, middleBackground;

    public float minHeight, maxHeight;
    public float cameraSpeed;

    private Vector2 lastPos;

    void Start()
    {
        lastPos = transform.position; // 카메라의 초기 위치를 저장합니다.
    }

    void Update()
    {	// 플레이어가 y축으로 움직일 때, 카메라를 범위 내에서 제한합니다.
        float clampedY = Mathf.Clamp(Player.position.y, minHeight, maxHeight); 
        
        // 카메라의 위치를 플레이어와 같은 위치로 맞춥니다.
        transform.position = new Vector3(Player.position.x, clampedY, transform.position.z); 
        
		// 카메라가 움직인 만큼 배경 이미지를 움직입니다.
        Vector2 amountToMove = new Vector2(transform.position.x - lastPos.x, transform.position.y - lastPos.y);
        
		// 먼 배경 이미지를 카메라와 같은 속도로 움직입니다.
        farBackground.position += new Vector3(amountToMove.x, amountToMove.y, 0f); 
        // 가까운 배경 이미지를 카메라와 같은 속도로 움직입니다.
        middleBackground.position += new Vector3(amountToMove.x, amountToMove.y, 0f); 
        
        // 카메라의 현재 위치를 저장합니다.
        lastPos = transform.position; 

    }
}

CameraController스크립트는 main camera 오브젝트에 컴포넌트 형태로 추가했습니다.

하나하나 뜯어서 살펴보겠습니다.

Transform 타입의 변수 3개(Player, farBackground, middleBackground는 public으로 선언되어 있습니다.

awake나 start와 같은 생명주기 함수에서 초기화하지 않고,

인스펙터 창에서 오브젝트를 할당해 초기화를 진행해 주었습니다.

 

Player변수에는 카메라가 따라다닐 Player 오브젝트를 할당했습니다.

farBackground와, middleBackground 변수에는 두 배경을 할당했습니다.

float minHeight변수와 maxHeight변수에는 카메라가 플레이어를 따라

수직이동을 하는 과정에서 한계선 지정을 위해 선언한 것입니다.

예를 들어, 플레이어가 낮은 지형으로 이동할 경우

빨간 박스 안에 땅 바깥의 부분이 게임창에 보일 수 있습니다.

반대로 너무 높이 점프하거나, 높은 지형에서는 하늘 바깥의 검은색 부분이 보일 수도 있습니다.

이를 방지하기 위해 높이의 한계선을 만들어 준 것입니다.

매 프레임마다 실행되는 생명주기 함수인 Update함수의 내부를 살펴보겠습니다.

먼저 float타입의 clampedY입니다.

Mathf.Clamp함수가 사용되었는데요.

Mathf.Clamp함수는 총 3개의 매개변수를 갖습니다.

첫 번째는 반환될 값, 두 번째는 최솟값, 세 번째는 최댓값입니다.

즉, Player 오브젝트의 Y좌표 중에서 minHeight, maxHeight에 속하는 값만 clampedY 변수에 할당됩니다.

 

transfrom.position은 카메라의 위치입니다.

매 프레임마다 카메라의 위치에 Player의 x좌표, clampedY에 할당된 Player의 y좌표가 더해집니다.

 

Vector2 타입의 amounToMove 변수에는

카메라가 얼마나 이동했는지 계산한 값이 할당됩니다.

 

Start함수는 생명주기함수로써 게임이 실행된 후 1번만 실행됩니다.

함수 안에는 latsPos라는 변수 안에 카메라의 위치가 할당된 것을 확인할 수 있습니다.

프레임마다 카메라의 위치를 추적하고, 그 위치에서 초기에 선언한 lastPos를 빼면 이동한 거리가 나옵니다.

카메라가 이동한 거리를 배경의 위치에도 더해주게 되면, 배경도 카메라와 함께 플레이어를 쫓게 됩니다.

마지막으로 lastPos 값에 카메라의 현재 위치를 할당하면서,

프레임 사이에 카메라가 플레이어를 쫓아 얼마큼 이동했는지 알 수 있습니다.

 

이렇게 하면 배경 오브젝트를 수없이 생성하지 않아도,

게임 화면에서는 배경이 무한히 반복되는 것처럼 보이게 할 수 있습니다.

반응형

+ Recent posts