프로그래밍/Objective-C2010. 7. 14. 09:38
* View의 생성방법

  1. 코드로 생성하는 방법  
     - UIView * 객체명 = [[UIView alloc]initWithFrame : (CGRect)rect];
        rect 부분은 CGPoint Origin(CGFloat x, y), 또는 CGSize Size(width, height) 등으로도 가능
     - [window 객체 addSubview : 뷰 객체명];
     - 코드로 생성시, xib 파일을 사용하는 부분이라면 m 파일의 loadview 부분에서 생성하면 안되고 viewDidLoad 부분에서 해야 한다.

- (void)viewDidLoad 
{
UIButton * button = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, 150, 30)];

[button setTitle:@"버튼" forState:UIControlStateNormal];   // 버튼의 초기 타이틀 설정. 

[self.view addSubview:button];   // 버튼을 현재 뷰 컨트롤러의 뷰의 서브뷰로 초기화함
[button release];    // SubView가 NSArray * 타입이라서 추가나 제거가 될 때 
                                    // retain과 release를 자동으로 수행하게 된다.
                            // 따라서 임시 변수를 이용해서 대입했다면 반드시 release를 해야 한다.
[button addTarget:self action:@selector(redView) forControlEvents:64];    // 버튼의 TouchUpInside  
                                                                                                                   // 이벤트에 redView 메소드를 
                                                                                            // 연결
    [super viewDidLoad];
}

-(void)redView
{
self.view.backgroundColor = [UIColor redColor];
}

  2. 인터페이스 빌더로 생성하는 방법
     - 인터페이스 빌더로 뷰를 생성하고 IBOutlet으로 멤버 변수와 연결한다
     - 만들기는 가장 쉬우나 사후 관리가 귀찮음

  3. UIView로부터 상속받는 클래스를 생성해서 사용자 정의 뷰를 만든 후 사용하는 것
     - 사람마다 필요로 하는 기능이 모두 조금씩 다를 수 있기 때문
     - 만들고자 하는 것이 기본적인 모양이 아닐 경우 이 방법을 사용해 만든다
     - 주로 테이블 뷰에 가장 많이 사용하게 된다(사람들마다 셀을 사용하는 형태가 다르기 때문)


* Objective-C의 콜렉션 : NSArray, NSDictionary, NSSet
  - 삽입이나 삭제시에 retain 과 release 이용


*  IBAction과 void는 기본적으로 같다. 인터페이스 빌더에서 인식하느냐 못하느냐의 차이일 뿐. 둘 다 리턴값이 없다.


* 인터페이스 빌더의 버튼 이벤트를 코딩으로 구현하려면 열거형으로 해서 값을 줘도 된다. (UIControlEvents 도움말에 잘나와있음)


* 앱 만들때 배경그림 까는 법
  - 인터페이스 빌더에서 Image View를 배경화면 크기로 깔아버리고 시작


* NSArray 오브젝트들의 배열

  1. 생성
    arrayWithObject : 객체
    arrayWithObjects : 객체 나열, nil
    arrayWithArray : 배열명

NSArray * ar = [NSArray arrayWithObjects : @"1", @"2", @"3", nil];
NSArray * ar1 = [NSArray arrayWithArray : ar];    <-- COPY이다.

ar1 = ar;
ar1 = [ar retain];      <-- 동작결과는 똑같지만 이게 더 좋은 방식. release에 있어서 더 자유롭다.

  2. 초기화
   array -> init
   공간은 있지만 데이터가 없는 경우임

  3. 추가 및 삭제
   -(NSArray *) arrayByAddingObject : 객체
   -(NSArray *) arrayByRemove : 객체

원래 3까지 있는 배열 ar을 만들고

[ar arrayByAddingObject : @"4"] <-- 이렇게 하면 가상본을 만들어서 4를 추가한다.
ar = [ar arrayByAddingObject : @"4"] 로 하여 다시 한번 ar에 집어넣어야 한다.

추가를 해서 처음에 정해진 배열크기보다 큰 부분에 데이터가 들어오면 자동적으로 원래 배열이 아니라 다른 곳에 큰 배열을 만들어서 추가해 버린다(위에서 말한 가상본). 실제 기기에서는 이런 부분이 메모리를 낭비하게 되어 아주 치명적인 누수를 일으킨다. 따라서 위의 방법은 사용하지 않는다.

NSArray * temp = [ar arrayByAddingObject : @"4"];
...
[ar release];

ar = [temp retain];
[temp release];

이것이 Objective-C에서 가장 일반적으로 배열을 만들고 추가/삭제를 하는 방법이다.

  4. 멤버 접근

   1) index 이용 :
      -(id)ObjectAtIndex:(id)index
      멤버 접근에 가장 쉽고 일반적인 방법

   2) Enumerator 이용:
      -(NSEnumerator *)ObjectEnumerator
      반복자. 어떤 형태의 멤버에든 접근하기 쉽게 하기 위해서 만들어진 것.
      배열은 규칙적으로 반복되기 때문에 읽기가 쉬우나, 실제 프로그램 작성시에는 그렇게 자료가 규칙적인 경우가 많지 않다.
      이 때문에 반드시 배워야 하는 개념이 Linked List. 이 경우에는 규칙적이지 않으므로 인덱스를 사용하지 못한다.
      무엇인가에 각각의 멤버의 주소만 전부 가지도록 하게 만들면 그 이후부터는 그것만으로 모든 멤버를 참조할 수 있다
      
NSEnumerator * 변수 = [ar ObjectEnumerator];

id sender;

while((sender = [변수 nextObject])!=nil)
{
...
}

   3) for ~ in 사용가능:

id sender;   // <-- 데이터 타입이 무엇이 들어있을지 모르기 때문에 대부분의 타입을 받을 수 있는 id 형을 사용

for(id sender in ar)

    자료들의 크기가 커지면 커질수록 속도면에서 유리해지는 방법이다.


  5. 배열의 요소 개수

    -(NSInterger) count



* 2차원 배열

  - Objective-C에서는 기본적으로 2차원 배열이 없다.
  - 때문에 2x2의 배열을 만든다고 할 경우는 이렇게 한다.

NSArray * ar1 = [NSArray arrayWithObjects: @"1", @"2", nil];
NSArray * ar2 = [NSArray arrayWithObjects: @"3", @"4", nil]; // 2개의 1차원배열을 만든 뒤

NSArray * ar = [NSArray arrayWithObjects: ar1, ar2, nil]; // 그 배열 2개를 모아 다른 배열을 선언

[ar1 release];
[ar2 release];

  - 주의

NSArray * copyAR = [NSArray arrayWithArray: ar];
ar = [ar arrayByAddingObject: ar3];
[[ar ObjectAtIndex: 0] arrayByAddingObject: @"10"];

  - 흔히 얕은 복사라고 함. 한번의 복사만을 해준다.
  - 이것을 방지하기 위해서는 메모리 상의 처리보다 파일처리 같은 외부 기억장소로 빼내서 처리하는 방법을 이용하는 것이 좋다


* ImageView

  - 이미지 한 장을 화면에 출력하기 위한 정적인 뷰. 이벤트 사용할 수 없다
  - Image 속성을 이용해 이미지 리소스를 연결한다
  - contentMode 속성으로 배치 모양을 결정한다
      UIViewContentModeScalToFill : 프레임의 긴 쪽에 맞춰서 확대/축소
      ScaleAspectFit : 작은 쪽에 맞춰줌 . 가로/세로 비율을 맞춰서 확대/축소

Posted by windship
프로그래밍/Objective-C2010. 7. 12. 09:29
* 컨트롤
  - UIKit가 제공하는 재사용 가능한 작은 부품
  - 일종의 뷰라고 할 수 있다
  - UIButton, UIDatePicker, UIPageControl, UISegmentControl, UITextField, UISlider, UISwitch...
  - 능동 컨트롤 : 사용자의 실제 이벤트를 연결하는 컨트롤. 일반적으로 버튼 등이 해당
  - 정적 컨트롤 : 
  - 수동 컨트롤 : 일반적으로 텍스트 필드 등이 해당

* UILabel
  - 정적 텍스트를 출력하기 위한 읽기 전용 뷰로 UIView로부터 상속. 컨트롤이 아님.
  - UIresponder로부터 상속받은 속성은 사용이 가능하지만 UIControl에 있는 이벤트는 사용 불가
  - 간단한 텍스트를 출력하기 위한 용도로 사용
  - lineBreakMode 프로퍼티가 중요. 출력 영역을 벗어났을 때의 처리.
  - numberOfLines : 몇줄까지 출력 가능한가라는 의미

* 타이머(NSTimer)
  - 일정 주기, 일정 시간에 대한 기능을 사용할 때 쓰는 클래스
  - NSTimer.h에 정의되어 있으나, Foundation에 포함되어 있으므로 기본적으로 인클루드는 안해도 된다.
  - +(id) scheduledTimerWithTimeInterval : 실행주기 Target : 수행할 메소드가 있는 객체 selector : @selector(수행할 메소드) userinfo : (id)부가 정보를 저장할 객체 repeats: (BOOL) 반복여부];
  - 수행할 메소드가 있는 객체 : 대개는 self
  - 수행할 메소드 : 시간이 되었을 때 수행할 명령. 매개변수도 없고 리턴값도 없어야 함
  - 반복여부 : yes / no
  - 타이머 종료시에는 invalidate 메소드를 호출해서 무효화시킴.

* 인터페이스 빌더로 만든 것은 dealloc으로 해제
* 프로퍼티를 만든 것은 synthersize로 해제

* UITextField
  - 사용자로부터 키보드 입력을 받기 위한 컨트롤
  - 문자가 아니라 숫자만 입력하면 될 경우는 Number Pad로 변경한다

* 키입력시 키보드가 화면을 가려버리는 문제의 해결
  1. 특정 키를 누를 때 키보드에 할당된 입력 이벤트를 강제로 회수해 버리면 된다
      - < UITextFieldDelegate>프로토콜을 따르도록 설정하고 (Bool)textFieldShouldReturn:(UITextField*)theTextField 메서드에서 매개변수가 resignFirstResponder 메서드를 호출
  2. Did End on Exit 이벤트를 이용
  3. 텍스트 필드에 이벤트가 발생할 때 버튼을 화면 위로 올리
  4. 뷰 클래스나 뷰 컨트롤러 클래스에서 터치 이벤트를 이용해서 텍스트 필드에서 FirstResponder를 해제


Posted by windship
* 아이폰 프로젝트의 구조

  - MainWindow.xib : UI가 만들어지는 출발
  - File's Owner : 각종 디자인 객체들을 제어하기 위한 클래스  
  - First Responder : 현재 활성화되어 사용자로부터 무엇인가 리액션을 받고 있는 객체의 클래스. 어떤 개체(예를 들어 키보드)가 First Responder가 되면 가상키보드가 뜨고 사용자로부터 입력을 기다린다. 프로그램에서 키보드의 First Responder를 해제하면 자동으로 키보드가 사라진다
  - XCode와 인터페이스 빌더는 별개의 프로그램이므로, 인터페이스 빌더에서 만든 객체와 XCode 상의 코드를 연결시키는 작업을 해 주어야 한다. 하지만 XCode의 모든 코드들이 인터페이스 빌더로 넘어오지는 못한다. 이 때문에 IBOutlet(변수)과 IBAction(메소드) 키워드가 존재한다. 이것이 붙으면 인터페이스 빌더에서 인식하게 되며, XCode상에서는 별다른 의미를 갖지 못한다(타입도 void임)
  - 주 실행이 되는 main 함수는 main.m에 위치하게 된다
  - AppDelegate : 주 프로그램 실행을 위한 처리들을 담당. 시작하기 전에 팝업창을 띄워 뭔가를 확인한다든가 할 때에 사용한다. 다른 모든 클래스는 File's Owner에 있지만 유일하게 윈도우에 대한 것은 AppDelegate에 있다
  - UIApplication : 아이폰에서 실행되도 좋은 프로그램인지 검사
  - WillTerminate : 종료 직전에 호출되는 메소드. 프로그램 종료 후 나중에 다시 실행했을 때 마지막에 하고 있던 작업이 그대로 남아있게 하는 처리를 여기에서 한다. 같은 이유로 프로그램 종료시에 없어져야 하는 객체, 해제해야 하는 메모리의 작업도 여기에서 해 주어야 한다. 이때 뜨는 윈도우의 디자인을 바꾼다거나 할 때에는 //Override point for customization after app launch 주석 바로 다음에 해준다.
  - [window addSubview:viewController.view];  <-- 뷰 위에는 얼마든지 다른 것이 올라갈 수 있지만 윈도우는 하나밖에 깔 수 없다


* 인터페이스 빌더의 xib 편집창
  - 기본적으로 항상 가운데의 계층형으로 창 표시 타입을 놓고 보는 버릇을 들이자


* 변수 연결하기
  - IBOutlet으로 지정해서 인터페이스 빌더와 연결
  - dealloc으로 해제


* Inspector 창 - 첫번째 탭 속성창
  - Drawing - Opaque를 체크 : 뒤쪽을 투명하지 않게 하므로 속도가 빨라진다. 다만 텍스트 필드에서는 사용하지 않는 것이 좋다
  - Clear Context Before Drawing : 무엇인가 객체를 그릴 때 뒤쪽을 지우고 새로 그리는가 아닌가를 설정
  - Clip Subview : 화면상에서 보이지 않는 부분을 그리지 않는다. 
  - Interaction - User Interaction Enabled : 유저 터치 입력을 받는다. 항상 켜두어야 함
  - Multiple Touch : 멀티터치 입력을 가능하게 한다


* XCode - .plist 파일
  - 아이콘에 쓸 png파일은 Finder에서 찾아서 XCode의 Resources 트리에 끌어다 놓으면 등록된다
  - plist 파일의 Icon file 란에 등록한 파일명을 입력하면 완료
  - 아이콘 사이즈는 60x60 이상을 넘어가면 안된다
  - 이미지는 기본적으로 확대/축소에 기대하지 말고 큰 것과 작은 것을 두개 다 준비해 두는 것이 좋다
  - Bundle이라는 용어가 많이 등장하는데 기본적으로 '내 애플리케이션'을 일컫는다고 생각하면 된다
  - Bundle Version을 사용해서 버전번호를 표기한다. 업데이트가 필요할 때에도 아이폰이 이 버전번호를 비교하여 업데이트 여부를 유저에게 알려주게 된다
  - plist 파일은 우클릭한 뒤 Open as -> Plain Text File 하면 XML 파일로 읽게 된다


* 디자인 패턴
  - 이벤트를 생성하는 것을 디자인 패턴이라고 한다

  - Delegation : 하나의 객체가 delegate 지정된 다른 객체에 주기적으로 메시지를 전달 -> 메시지를 처리할 수 있다면 처리해 달라고 요청하는 것
  - 상위 클래스로부터 모두 상속받아서 사용하는 것이 아니라, 원하는 클래스 이름에 delegate를 붙여서 그 클래스의 원하는 부분만 선택(프로토콜)하여 받아오는 것
  - -(void)applicationDidFinishLaunching:(UIApplication *)application
  - -(void)applicationWillTerminate:(UIApplication *)application

  - MVC 패턴 : 모션, 뷰, 컨트롤러로 구별해서 동작을 정의한다 . 테이블 뷰 형식의 프로그램을 이 방식으로 한다.

  - Target-Action 패턴 : 객체에 메시지를 보내서 객체를 제어하게 한다. 일반적으로 이벤트 처리에 가장 많이 사용된다
  - IBAction 키워드를 사용해서 넘기면 이 방식이 된다 . 리액션에 따라 개체가 바뀌는 경우(눌리는 버튼 등)에는 원래의 자기 객체가 무엇인지도 기억해놓고 있어야 하므로 -(IBAction) click(id)sender와 같이 sender를 사용해줘야 한다. 이 때에는 형변환을 해줘야 한다
  - 코딩으로 만들어도 된다. [객체 AddTarget : 메소드소유객체명 action : @selector(메소드명) forControlEvents : (UIControlEvents) 이벤트 이름 과 같은 형식이 된다
  - 소유 객체명은 Target-Action 방식에서는 거의 self가 되지만 MVC 패턴에서는 소유 객체와 구현부가 다르므로 self를 사용할 수 없다는 것에 주의


* 윈도우기반 어플리케이션
  - 기본적으로 윈도우 하나만이 존재한다
  - 만든 뒤 UIViewController의 서브클래스를 추가
  - 새로 클래스파일을 추가할 때에 3개의 옵션 체크가 중요하다(xib 파일도 함께 만드느냐 그렇지 않느냐 등등)
  - xib 파일도 함께 만들면 처음에는 classes 폴더에 함께 생기므로 Resources 폴더로 옮겨 준다


* 예제 - AppDelegate.h

//
//  _709_2AppDelegate.h
//  0709_2
//
//  Created by iMac2_13 on 10. 7. 9..
//  Copyright __MyCompanyName__ 2010. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "TestViewController.h" // 추가한 뷰 컨트롤러의 헤더 파일을 import함

@interface _709_2AppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
TestViewController * viewController; // 윈도우에 추가할 뷰 컨트롤러의 변수 선언
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@property (nonatomic, retain) TestViewController * viewController; // 추가한 뷰 컨트롤러의 멤버에 쉽게 접근하기 위한 프로퍼티 설정

@end


* 예제 - AppDelegate.m

//
//  _709_2AppDelegate.m
//  0709_2
//
//  Created by iMac2_13 on 10. 7. 9..
//  Copyright __MyCompanyName__ 2010. All rights reserved.
//

#import "_709_2AppDelegate.h"

@implementation _709_2AppDelegate

@synthesize window;

@synthesize viewController; // 프로퍼티로 지정한 변수의 synthersize 지정


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    // Override point for customization after application launch

viewController = [[TestViewController alloc]init]; // 추가한 뷰 컨트롤 변수에 메모리 할당, 초기화
[window addSubview:viewController.view]; // 윈도우에 추가한 뷰 컨트롤러의 뷰 붙이기. 중요함!
    [window makeKeyAndVisible];
return YES;
}


- (void)dealloc {
[viewController release]; // 지정한 인스턴스 변수 release
    [window release];
    [super dealloc];
}


@end


* 이벤트 객체 알아내기

  - IBOutlet 변수 이용하기
  - 어떤 메소드로부터 호출된 것인지 알기 위해서 Tag라는 속성을 줄 수도 있다. 다만 이 경우에는 절대로 같은 Tag를 설정해 주면 안된다. 
  - Tag를 사용하게 되면 IBOutlet은 쓸 필요가 없어진다
  - 
Posted by windship
* selector

  - 오브젝티브 C에서 C 언어의 함수 포인터나 다른 언어에서의 delegate에 해당하는 기능
  - 다형성을 구현하기 위해서 사용.
  - 메소드를 실행시 동적으로 결정하기 위한 개념. 이 때문에 없는 매개변수를 참조하거나 하면 에러 메시지를 발생시키는 것이 아니라, 실행하고 나서야 변수를 찾아보고 없으면 바로 튕겨 버린다
  - 자료형일 때에는 SEL
  - 대입할 때에는 @selector(메소드명)으로 메소드의 주소를 리턴받아서 대입시킴
  - 매개변수가 있을 때에는 @selector(메소드명:)   <-- 콜론이 붙는 것에 주의
  - 셀렉터에 지정되는 메소드는 반드시 매개변수가 객체여야 한다.

-(void)Disp;                 <- 가능
-(void)Disp:(id)sender; <- 가능
-(void)Disp:(int)n;       <- 불가능

  - 사용예
    [ 객체 performSelector : (SEL) 변수];

    SEL dele;
    if(조건)
      dele = Disp;
    else
      dele = Print;

    [객체 performSelector : dele];


[객체 addTarget : delegate action : @selector(메소드)];

* 다형성 구현에 대한 개념

if(sel == 1)
  dele = @selector(Disp);
else
  dele = @selector(Print);
[Obj performSelector : dele];

  - 위 소스에서 sel 이 1이라면 Disp를, 아니라면 Print를 수행한다
  - 이와 같이 아래의 한 문장이 여러 가지 기능을 수행할 수 있도록 하기 위해 다형성을 구현한다
  - 보통의 C 에서는 오버라이딩을 통해 다형성을 구현하지만 이것은 상속받지 않으면 사용할 수 없다는 단점이 있다
  - 그 때문에 오브젝티브 C에서 새로 도입된 개념이 selector 이다

@interface Test:NSObject

-void(Disp);
@end

@implements Test
-void(Disp)
{
  NSLog(@"Hello");
}
@end

int main()
{
  Test * Obj = [Test new];
  [Obj Disp];

  if([Obj respondsToSelector : @selector[Disp)] == YES)
    [Obj Disp];
  else
    {예외 발생};

  return 0;

  - Disp라는 함수가 구현이 되어 있을 경우는 문제가 없지만 위와 같이 코딩을 해두면 Disp가 없어서 예외가 발생했을 경우에 대한 처리가 가능해진다
  - 기본적으로 여러 사람에 의한 공동 작업을 상정해 만들어진 기능이라고 생각하면 된다

* 카테고리

  - 클래스의 기능을 확장하기 위해 일부 메소드를 구현한 모듈을 말한다 . 사용자 정의 클래스나 프레임워크가 제공하는 클래스의 기능을 확장하고 싶을 때에 사용한다(즉 기본 SDK에서 제공되는, 사용자가 마음대로 고칠 수 없는 것들을 좀 편하게 사용할 때에 주로 사용한다)
  - 기존 클래스의 정의를 변경하지 않은 상태에서 원하는 메소드만 추가하여 사용할 수 있다
  - 인스턴스 변수의 선언은 할 수 없다(할당받는 메모리의 사이즈가 달라져 버리므로)
  - 기본적으로 원래의 클래스를 import로 가져온 뒤 별도의 소스 파일을 하나 추가해서 두개를 더한 클래스를 만들어 버리는 방식이다

* 프로토콜

  - 아이폰 SDK의 프로토콜 이름은 거의 대부분 사용자가 유추할 수 있다

<클래스명 Delegate>
<클래스명 Datasource>

  - Did나 Will로 시작되는 것들도 많다
  - 어떤 메소드를 사용해야 하는지를 프로토콜 이름을 보고 추정하여 찾아가는 습관을 갖는 것이 좋다
  - 프로토콜 적용

@interface 클래스명 : 슈퍼클래스명<프로토콜 이름>

  - 프로토콜도 상속이 가능하며, 콤마로 구분하여 여러개를 붙여서 다중 상속도 가능하다

* 인스턴스와 매개변수의 제약
  - 인스턴스 변수 선언시 오브젝티브 C는 id 타입으로 선언하면 아무 제약 없이 모든 클래스의 인스턴스를 생성할 수 있다
  - 이 때 생성되는 인스턴스에 제약을 가하고자 하는 경우에도 프로토콜을 사용할 수 있다. 이렇게 되면 동적이 아니라 정적으로 타입을 점검하게 된다

* 프로토콜의 선택/필수 구분
  - 프로토콜에 선언되는 메소드 앞에 @optional을 적으면 메소드를 구현해도 되고 안해도 된다
  - @required라고 쓰면 반드시 구현해야 한다

* NSObject
  - 최상위 클래스로서 모든 클래스는 여기에서 상속받아 사용해야 한다. 그렇지 않으면 인스턴스를 만들 수 없다

* 메모리 생성과 해제
self = [super init];    // <-- 메모리를 초기화
if(self != nil)        // <-- 초기화가 잘 되었는지 확인
{
   처리내용
}
return self;

  - 굉장히 정형화된 표현으로서 거의 대부분 저런식으로 이루어진다
  - 유저는 거의 처리 내용만을 입력하게 된다

UIButton * button = [[UIButton alloc]init];
버튼 사용
release

  - 직접 코딩으로 버튼을 만드는 경우. 이 경우는 메모리 할당과 초기화 한번으로 끝난다

IBOutlet UIbutton *button
버튼 사용
[[UIbutton alloc]init]retain]
button release

 - 인터페이스 빌더로 만들고 나면 IBOutlet으로 들어가게 되는데, 이 경우는 메모리를 할당한 뒤 그것을 복사하여 2개가 된다. 다 사용하고 뷰를 없앴을 때에는 아래쪽에서 retain 문으로 retain Count가 1 감소하게 되지만 원래의 하나는 남아 있기 때문에 메모리 누수의 원인이 된다. 이런 식으로 했을 때는 반드시 button release를 한번 더 해줘야 한다.

* iPhone SDK

  - 프로젝트 만들 때에 Navigation Based는 주소록 기반이라는 뜻
  - OpenGL ES : 그래픽 처리 관련
  - Split View Based : 아이패드용
  - Tab Bar : 보통 잘 안쓴다. Tab bar는 Navigation Based를 포함할 수 있지만 반대는 안된다.
  - Utility : 주식효과 같은 프로그램에 사용. 화면이 앞뒤로 뒤집어지는 처리를 기본으로 함. 모양새일 뿐 별로 의미는 없다.
  - View Based : 기본적인 화면 하나를 두고 거기에서 처리하는 프로그램.
  - Window Based : 창 하나를 띄우고 거기에 추가하여 만드는 프로그램. 가장 추천이 되고 기본이 되는 타입.

* 프로젝트 설정 후 왼쪽 폴더
  - Classes : 가장 많이 코딩하게 되는 곳.
  - Other Sources : 프로젝트 실행에 관련된 파일들이 존재
  - Resources : 각종 그래픽이나 사운드 등의 외부 자원들이 등록되는 곳. 
                       XIB 파일 : 화면처리와 디자인에 관한 것들
                       plist : 
  - Frameworks : 참조해야 하는 헤더 파일들의 모음
                         UIKit
                         Foundation
                         CoreGraphics
                         위 3가지 이외에도 필요에 따라 필요한 프레임워크를 링크하여 사용할 수 있다.
  - Products : 유저가 만드는 앱이 등록되는 곳.

* 인터페이스 빌더
  - Library : 끌어다 놓음으로서 만들 수 있는 것들.
view에 끌어다 놓아서 화면의 디자인을 할 수 있는 것들이 있고, xib 파일에 끌어다 놓는 컨트롤과 같은 것들도 있다.
  - Inspector : 설정창. 아주 중요하다.
      Attributes : 각종 속성을 설정한다
      Connections : View상에 배치된 객체와 연결시켜서 속성을 설정
      Size : 객체의 위치와 크기 등의 설정
      Class : 객체의 제어권 설정
  - 하나의 작업이 끝나면 인터페이스 빌더는 바로 저장하고 꼭꼭 닫아주는 버릇을 들이는 것이 좋다. 창이 계속해서 새로 떠 버리기 때문에 헷갈리게 된다. 

* 아이폰 시뮬레이터
  - 그냥 Run 하면 나온다(...)
  - 프로젝트를 하나씩 지우는 것은 실제 아이폰에서 하듯이 하면 됨 (아이콘 오래 클릭)
  - 화면 회전은 Command + 방향키
  - 멀티 터치는 Option + 마우스 드래그
  - 왼쪽 위의 버전은 대개 시뮬레이터로 세팅. 3.2는 아이패드용이므로 쓰지말자(...)

* 외부와 연결시 변수는 IBOutlet으로, 액션은 IBAction으로 연결되어야 한다.
Posted by windship
* iPhone에서의 상속은 거의 모두 NSObject 또는 NSProxy로부터 받는다

* id 타입이란 일반 C 코딩에서의 "주소"와 거의 같은 개념이다.

* 실행 중간의 디버깅 및 값 확인은 NSLog를 써서 콘솔 창에서 확인하는 것이 좋다.

* 유저가 만드는 클래스는 크게 2가지 뿐.
  - 데이터 모델 (구조체)
  - 상위로부터 상속받아 사용하는 클래스

* delegate는 상위 클래스나 SDK에 구현되어 있는 기능을 빌려와서 사용할 때 쓰는 것. 거의 대부분은 self(C++ 에서는 this)를 통해 자신을 참조하게 하여 사용한다. 

* 상위 클래스의 멤버와 같은 이름의 멤버를 하위 클래스에서 선언, 또는 재정의(오버라이딩)하면 상위 클래스의 원래 멤버가 가려져서 부를 수 없게 되므로, 자신이 정의한 메소드는 this를 이용해 호출하고 상위 클래스의 원래 멤버는 super를 이용해서 호출해야 한다.

* 대개 아이폰 SDK에 구현되어 있는 기본적 기능을 이용할 경우(메모리나 화면에 관련된 처리) 이러한 super를 이용해 상위 클래스의 멤버를 호출해 오버라이딩을 사용한다.

* typedef
  - 자료형에 별명을 붙일 때 사용하는 예약어.
  - typedef [대상자료형] [별명으로 사용할 이름];

* 전처리기(Preprocessor)
  - 일반적 C와 같음
  - #으로 시작함
  - 꼭 오브젝티브 C의 문법일 필요는 없음
  - 끝에 ; 표시를 쓰지않음

* #define
  - 상수값에 심벌명을 부여한다
  - 연산자의 재정의도 가능. 즉 #define AND &&, 또는 #define SAME == 와 같은 것도 가능하다. 

* 매크로 함수
  - #define 문을 사용해서 수식 을 대치함.
  - 실제의 함수는 아니지만 마치 함수처럼 사용할 수 있기 때문에 매크로 함수라고 부른다.
  - 매크로명을 모두 대문자로 써서 구분하는 것이 일반적.

* C의 배열은 사용가능하지만 가능한 한 사용하지 않는 것이 좋다.
  - 경계가 없다. 첨자가 3인 배열에서 -1이나 10도 넣거나 읽을 수 있다.
  - 값을 잘못 넣으면 기존에 있는 변수의 값을 건드려 바꿔버릴 수도 있다.

* CGPoint, CGRect, CGSize

CGPoint pt;
pt.x = 10;
pt.y = 20;

CGPoint pt = {10, 20};

CGPoint pt = CGPointMake(10, 20);

 CGRect
 {
    CGPoint origin;
    CGSize size;
  }
Posted by windship
* 오브젝티브 C의 클래스
  - NSObject 클래스로부터 상속받는다
  - 선언부와 구현부가 분리되어 있다

* 선언부
@interface 클래스명 : 상위클래스명                            // 클래스는 일종의 Template
 {
      멤버변수 선언;
 }
 +- 메소드 선언;
 @end
 
  - static이나 const를 내부에서 선언할 수 없다. 오로지 멤버 변수만 가능.

* 구현부
@implements 클래스명
+- 메소드 구현
@end

@class NSObject
#import <Foundation / Foundation.h>

* 클래스 선언과 사용
  - 클래스는 무조건 포인터로 받는다
  - 클래스명 * 객체명;       <-- 객체 선언이지만 이 상태에서는 사용할 수 없다.
  - 객체명 = 다른 객체;     <-- Assign
  - 객체명 = [다른 객체 retain];           <-- Retain. Assign과 동작결과는 같지만 복사 구문이 아니다. 참조 카운터만 1 감소시키며,
                                                                할당된 메모리는 그대로 남아 있다. 참조 카운터가 0이 되었을 때 비로소 메모리가 해제
                                                                된다. 메모리 누수의 주된 원인 중의 하나이므로 반드시 메모리 해제를 해 줄것. 특히
                                                                인터페이스 빌더 등을 사용할 때 빈번하게 일어난다.
  - 객체명 = [다른 객체 copy];
  - 객체명 = [클래스명 alloc];             <-- 순수하게 메모리 할당만 받는다
  - 객체명 = [클래스명 new];              <-- 메모리 할당을 받고 초기화까지 해준다
  - 객체명 = [클래스명 allocWithZone : (NSZone *)zone];            <-- 하나의 Heap을 생성한다. 아이폰4의 멀티태스킹 작업에
                                                                                                          유리하게 사용될 것이다.
  - 객체명 = [클래스명 ? with ?];        <-- with라는 단어가 나오면 무조건 생성자나 초기화 역할을 하는 것. 앞에 무엇이 붙는가가 중요하다. 앞에 init가 붙지 않으면 생성자이며, init가 붙으면 초기화이다.

* 같은 기능을 하는 메소드지만 여러가지 종류가 있을 수 있다. 예를 들면
   awakeFromNib
   loadview
   viewDidLoad
  와 같은 것들인데, 결과는 같지만 실행 우선순위와 메모리 해제 시점이 다르기 때문에 이에 따라서 먼저 실행되는 부분이 결과가 나타나지 않는 경우가 많이 발생한다. 이 경우에는 retain 이 사용된 부분을 가장 우선적으로 검토하는 것이 좋다.

* 클래스 추가
  - XCode > File > New File > iPhone OS/Mac OS X > Cocoa Touch Class > Objective-C Class > NSObject를 선택.
  - 이름을 결정하면 class.h와 class.m이 바로 만들어진다.
  - 그러나 대개는 XCode의 왼쪽 구조창 중 class부분에서 오른쪽 클릭을 눌러 추가하는 방식이 일반적이다.

* 함수와 메소드의 차이
  - 함수 : C 스타일 함수, 또는 매크로 함수
  - 메소드 : 클래스 안에 선언된 함수
  - 오브젝티브 C에서는 기본적으로 C 스타일의 함수는 만들지 않는다. 만든다면 거의 매크로임.

* 메소드 선언
(결과형) 메소드 이름 : (자료형) 매개변수 이름  별명 : (자료형) 매개변수 이름

예) -(void) UIApplication : int XXX Section int YYY

  - 메소드 이름 부분은 대개 "누구", "어디" 라는 타겟, 장소를 의미하게 된다.
  - 별명 부분은 대개 "동작"을 의미한다.
  - " - "나 " + "를 붙인다.
  - " - "를 붙일 경우 의미가 인스턴스라는 뜻. 즉 멤버라는 뜻이 된다.
  - " + "가 붙을 경우 클래스라는 뜻.
  - 오버라이딩은 있지만 오버로딩은 없다.
 
* 변수
  - 인스턴스(멤버) 변수 : 클래스 내에서 선언되는 변수. 이 인스턴스 변수에만 접근 지정자 선언이 가능하다. (메소드나 클래스에는 사용 불가. 메소드나 클래스는 무조건 public이다)
     @private : 클래스 내부에서만 사용가능
     @protected : 클래스 내부에서, 그리고 상속받은 클래스에서도 사용가능
     @public : 클래스 내부, 상속받은 클래스, 객체에서 사용가능

  - 지역(Auto)변수 : 메소드 내에서 선언되어 그 안에서만 사용된다. Auto 키워드는 쓰거나 안쓰거나 자유지만 가능한 한 안쓰는 게 좋다.
  - static 변수
  - const 변수
  - static과 const는 File의 개념이므로 큰 의미가 없다
  - extern은 자료 교환을 위한 타입이지만 없어지지 않으므로 위험하다. (윈도우 모바일의 DLL과 비슷함. 한번 로드하면 메모리 해제를 해도 사용만 못하게 될 뿐 계속 남아있으므로 문제가 크다. 거의 사용할 필요가 없음.)

* getter와 setter의 개념
  - 멤버 변수와 메소드의 이름이 같아도 된다(오브젝티브 C만의 특성).
  - getter는 멤버 변수의 이름과 동일하게 사용하는 것을 권장한다.
  - setter는 set 변수명 으로 사용하는 것을 권장한다. 변수명의 첫 글자는 대문자.

{
int result;
}

-(int) result;      //  <-- getter
-(void) setResult : (int) temp;      //  <-- setter

-(int) result {return result;}      //  <-- getter
-(void) setResult : (int) temp {result = temp;}      //  <-- setter

[Obj setResult : 10];
NSLog(@"%d", [Obj result]);


* 프로퍼티
  - 위 방식은 메소드 갯수가 너무 많아지는 단점이 있기 때문에 도입된 것이 프로퍼티이다
  - setter와 getter 를 대신해서 사용할 수 있는 개념

  - 선언 :
   
@property (특성) 자료형 변수명;

    이렇게 씀으로서 setter와 getter의 선언이 완료된다.
    예 : @property int result;
    이것은 스레드 관련 권한의 처리도 겸하기 있기 때문에 권장되는 기능이다.

  - 구현 :

@synthersize 변수명;

    property문을 보고 구현해주는 역할만을 함.
    예 : @synthersize result;

* 프로퍼티의 특성
  - non atomic : 멀티 스레드를 제어하지 않는다. (속도를 빠르게 함) 지금까지는 아이폰에서 멀티태스킹을 할 필요가 없었기 때문에 거의 대부분이 이것을 사용해 퍼포먼스를 좋게 했었지만 앞으로는 이것을 지양해야 할 것이다.
  - setter, getter : 특성은 존재하지만 거의 안쓴다
  - assign, retain, copy : 객체에만 적용된다. 일반 value형 변수들은 값만을 사용하므로 의미가 없고 heap 메모리를 사용할 때에만 필요하다. copy는 "얕은 복사"라고 많이 표현하는데, 배열의 마지막 데이터가 아니라 첫번째 데이터만을 복사한다. 전체를 다 복사하는 "깊은 복사"는 archiving이라고 구분하여 표현한다.
  - read only, read//write : 읽기 전용 속성과 읽기/쓰기 속성. 이런 것이 있기 때문에 const를 사용하지 않는다. 읽기/쓰기 속성은 디폴트 값으로서, 따로 지정해 주지 않으면 이 속성이 되므로 거의 쓰지 않는다.
  - 위 속성들은 " , " 기호로 함께 사용할 수 있다. 다만 그 특성상 함께 쓸 수 없는 것들도 있다.
  - " . " 기호를 이용해서 접근 가능. " = " 기호(assign, 즉 할당 연산자)의 왼쪽에 " . " 기호가 사용되면 자동적으로 set이 호출된다.
    즉,
    [Obj setResult : 10];
    위 구문은
    Obj.result = 10;
    이렇게도 사용할 수 있다.
  - @synthersize _result = result;  <-- C에서 많이 사용되는 관습적 문법. 가능은 하지만 오브젝티브 C에서는 일반적인 문법은 아니다.

* Static 변수
  - 클래스 내부에 생성해서 모든 객체가 공유하기 위한 변수
  - 근본적으로 객체가 사용하게 된다.
  - 객체들 간의 통신에 사용한다.

static int sequence = 1;

-(id) init
{
self = [super init];
if(self != nil)    // 메모리를 제대로 받았는가 검사
{
    result = sequence++;
{
return self;
}
   
 
Posted by windship
* import
  - C/C++의 include와 비슷하지만 몇 가지가 다르다.
  - 몇 번을 선언해도 한번만 실행됨.
  - 아이폰 프로그래밍에서는 거의 Foundation 하나만 import하면 끝난다.

* 앞에 NS가 붙는 것은 NextStep의 약자로 스몰토크 시절부터 내려온 함수라는 뜻.

* NSAutorelease : 아주 중요한 함수. 메모리 할당 후 나중에 drain으로 알아서 해제하겠다는 선언.
  -  [[NSAutorelease alloc] init] : alloc으로 메모리 할당. init로 초기화. 이 두가지를 한번에 하고 싶다면 new로 바꿔 쓸 수 있다.

* NSLog : C의 printf와 거의 같다.
  - NSLog(@"%d", 10);  <-- "%d"는 서식, 10은 데이터. 앞에 @가 붙는 것을 제외하면 같다.
  - C의 printf는 UTF-8 아스키 코드를 출력하지만 NSLog는 유니코드로 출력한다.

* 서식
  - %d, %i, %x, %o, %u
  - %f, %g
  - %c
  - %s : 주소부터 바이트 단위로 읽어서 null을 만날 때까지 문자로 출력
  - %@ : description 메소드 출력. C에는 없고 오브젝티브 C에만 있음. NSString, NSNumber, NSDate는 이것밖에 지원 안한다.

* 오브젝티브C에서는 기본적으로 데이터에 숫자처리는 없고 문자열로 취급한다.
  - 숫자데이터 10과 문자데이터 Hello가 있을 경우 이 둘을 합치고 싶으면 단순히 10 + "Hello" 해 버리면 됨.
  - 숫자데이터 10을 문자열로 바꾸고 싶을 경우 단순히 10 + "" 해버리면 됨.

* 자료형
  - 기본적으로 C의 자료형은 다 사용할 수 있다.
  - Value 타입 : C와 거의 같다. 없는 것은 BOOL 타입. BOOL 타입의 예:1, 아니오:0.
  - Reference 타입 : C의 포인터와 같다. 객체라는 뜻. 대개 주소라서 앞에 *을 붙이지만 id는 *가 붙지 않아도 주소로 받는다.
  - id는 C의 void *와 유사하다. 모든 데이터 타입의 주소를 기억할 수 있다. 동적 데이터 할당 - 자료가 들어올 때 그에 맞게 기억장소를 할당.

int a = 20;
float f = 10.7f;
void * vp = &a;
vp = &f;

  - 이 경우 printf("%d", *vp); 라고 하면 에러가 난다. void *는 시작주소는 기억하지만 사이즈는 기억하지 못하기 때문. 그래서 C언어에서는 (Cint *)vp)와 같이 vp를 int로 형변환을 해서 알아낸다.
  - 그러나 오브젝티브C에서는 자료 저장시에 이미 자료의 크기도 함께 저장한다. 따라서

id x = &a;
printf("%d", *x);

이렇게 하면 가능해진다.
 - 다만 id는 모든 자료타입을 다 받을 수 있기 때문에 차후 관리가 어렵다. 가능하면 사용하지 말고 형변환을 통해 사용하는 것을 추천.

* enum : 열거형 상수.
  - 아이폰 프로그래밍에서 매우 자주 사용하게 됨.
  - enum test {Zero, One, Two}; -> {K=10, Z, A} 와 같이 주었을 경우 Z는 11, A는 12가 된다(초기값부터 시작해 증가하게 됨).

* 연산자와 제어문은 C와 같다.

* 오브젝티브 C의 클래스
  - 선언부와 구현부가 분리되어 있다.
  - 선언부가 .h 파일에 들어가고, 구현부는 .m 파일에 들어간다.
  - 따라서 기본적으로 한 쌍으로 사용되며, 이름도 같은 것이 보통이다.

  - 선언 문법

@ interface 클래스명 : 상속되는 클래스                 // 반드시 상속되는 클래스가 필요하다
{
인스턴스 변수 선언;
}

+- (리턴타입) 메소드명;
@end

  - 구현 문법

@ implementation 클래스명                                   // 상속클래스명은 필요없다
+- (리턴타입) 메소드명;
{
정의
}
@end


* 메소드

  - "-"가 붙으면 인스턴스 메소드(객체).

(리턴타입) 메소드이름 : (매개변수 자료형) 매개변수명

+(void) disp : (int) a
{
    NSLog(@"%d", a);
}

  - 객체 생성
    클래스명 * 객체명 = [클래스명 alloc]; new

  - "+"가 붙으면 클래스 메소드.

Posted by windship
프로그래밍/Cocos2D2010. 7. 2. 17:57
비록 예제를 위한 게임이지만 3단계까지 진행한 상태에서는 별반 감흥이 없다. 이유는 바로 소리(배경음악, 효과음)가 없기 때문이다. 지금까지 게임 개발의 문외한 이었지만 "게임은 종합 예술"이라는 말을 자주 들었었다. 정말 그렇다. 아주 간단한 게임조차 그래픽과 사운드는 필수 요소이다.

사운드 샘플은 원문의 샘플을 그대로 사용하기로 한다. 아니면 Cocos2D에서 소개하는 게임 관련 리소스(Game Resources)를 이용해도 좋을 것 같다.

배경 음악과 효과음 추가
다음 두 개의 파일을 다운 받아 Xcode 프로젝트의 Resources 폴더에 추가한다.
우선 MammothHuntingScene.h에 다음 임포트 문을 추가한다.

#import "SimpleAudioEngine.h"


init 메소드에 다음과 같이 배경음악을 시작하는 코드를 추가하자. 마지막 부분에 추가하면 된다.

[[SimpleAudioEngine sharedEngineplayBackgroundMusic:@"background-music-aac.caf"];


그리고 ccTouchesEnded 메소드에 총알 발사 효과음을 추가한다.

[[SimpleAudioEngine sharedEngineplayEffect:@"pew-pew-lei.caf"];


빌드앤런! 이제 배경 음악과 효과음을 들을 수 있다.

게임 진행 로직 추가
다음은 간단한 게임 진행 로직을 추가할 것이다. 프로젝트에 GameOverScene 이라고 새로운 클래스를 추가하자. 그리고 다음과 같이 GameOverScene.h와 GameOverScene.m을 수정한다.

[GameOverScene.h]

@interface GameOverLayer : CCColorLayer {

CCLabel *_label;

}


@property (nonatomicretain) CCLabel *label;


@end




@interface GameOverScene : CCScene {

GameOverLayer *_layer;

}


@property (nonatomicretain) GameOverLayer *layer;


@end



[GameOverScene.m]

#import "GameOverScene.h"

#import "MammothHuntingScene.h"



@implementation GameOverLayer


@synthesize label = _label;



-(id) init {

if ((self=[super initWithColor:ccc4(255,255,255,255)])) {

CGSize winSize = [[CCDirector sharedDirectorwinSize];

self.label = [CCLabel labelWithString:@"" fontName:@"Arial" fontSize:32];

_label.color = ccc3(0,0,0);

_label.position = ccp(winSize.width/2, winSize.height/2);

[self addChild:_label];

[self runAction:[CCSequence actions:

 [CCDelayTime actionWithDuration:3],

 [CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)],

 nil]];

}

return self;

}



- (void)gameOverDone {

[[CCDirector sharedDirectorreplaceScene:[MammothHuntingScene scene]];

}



- (void)dealloc {

[_label release];

_label = nil;

[super dealloc];

}



@end



@implementation GameOverScene


@synthesize layer = _layer;



- (id)init {

if ((self = [super init])) {

self.layer = [GameOverLayer node];

[self addChild:_layer];

}

return self;

}



- (void)dealloc {

[_layer release];

_layer = nil;

[super dealloc];

}



@end


위 소스에서 한 가지 주목할 것은 하나의 파일에 두 개의 서로 다른 객체, 즉 Layer와 Scene이 있다는 점이다. 보통 하나의 Scene은 여러 개의 Layer를 포함할 수 있다. 그러나 본 예제에서는 오직 하나의 Layer만 사용한다. 화면의 중앙에 라벨을 그리는 Layer이다.

마지막으로, 단순한 게임 진행 로직 추가를 위해, MammothHuntingScene.h에 다음의 멤버 변수를 추가하자. 

int _projectilesDestroyed;


MammothHuntingScene.m에는 다음 임포트 구문을 추가한다.

#import "GameOverScene.h"


그리고 게임의 승리 조건을 판단하기 위해 update 메소드의 targetsToDelete 루프에 다음 코드를 추가한다.

_projectilesDestroyed++;

if (_projectilesDestroyed > 30) {

GameOverScene *gameOverScene = [GameOverScene node];

[gameOverScene.layer.label setString:@"You Win!"];

[[CCDirector sharedDirectorreplaceScene:gameOverScene];

}


다음 spriteMoveFinished 메소드의 sprite.tag == 1 조건 부분에 아래의 코드를 추가한다.

GameOverScene *gameOverScene = [GameOverScene node];

[gameOverScene.layer.label setString:@"You Lose :["];

[[CCDirector sharedDirectorreplaceScene:gameOverScene];


드디어 아주 아주 간단한 게임 개발이 끝이 났다. 다시 빌드앤런! 추가된 게임 로직이 제대로 작동될 것이다.

MammothHunting 프로젝트 소스
Posted by windship
프로그래밍/Cocos2D2010. 7. 2. 17:55
충돌 검사
이전 포스트까지의 내용대로 개발된 게임은 아무리 총알을 발사해도 타겟을 통과할 뿐이다. 이 문제를 해결하기 위해 충돌 검사라는 것이 필요하다.

케익에 맞은 맘모스는 게임 화면에서 사라져와 한다. 물론 잘 만들어진 게임이라면 총알 맞는 효과 같은 것들이 부수적으로 필요할 것이다. 

그러나 간단한 게임 예제라는 전제로 이런 부분들은 배제하고 간단히 총알과 타겟이 만났을 경우만 처리하는 코드를 작성할 것이다. 게임 물리의 거리, 운동 등의 다양한 면을 담당하는 게임 엔진의 한 부분인 물리 엔진은 사용되지 않는다.

먼저 다음 코드를 MammothHuntingScene.h에 추가하자.

NSMutableArray *_targets;

NSMutableArray *_projectiles;


그리고 MammothHuntingScene.m의 init 메소드의 self.isTouchEnabled = YES; 아래에 다음 코드를 추가한다.

_targets = [[NSMutableArray allocinit];

_projectiles = [[NSMutableArray allocinit];


마지막으로 메모리 관리를 위해 MammothHuntingScene.m의 dealloc 메소드에 다음 코드를 추가한다.

[_targets release];

_targets = nil;

[_projectiles release];

_projectiles = nil;


이제 addTarget 메소드르 수정할 차례이다. 위에서 선언한 _targets 배열에 새로운 target를 추가하는 것이다. 제일 아랫 부분에 다음처럼 추가하자. 새로 설정한 tag는 나중에 사용될 것이다.

target.tag = 1;

[_targets addObject:target];


그리고 ccTouchesEnded 메소드 역시 _projectiles 배열에 새로운 projectile을 추가하게 끔 다음 코드를 메소드 마지막에 추가한다. 새로 설정한 tag는 나중에 사용될 것이다.

projectile.tag = 2;

[_projectiles addObject:projectile];


마지막으로 spriteMoveFinished 메소드에 위에서 설정한 tag를 기반으로 스프라이트를 삭제하는 코드를 추가한다.

if (sprite.tag == 1) { // 타겟.

[_targets removeObject:sprite];

else if (sprite.tag == 2) { // 총알.

[_projectiles removeObject:sprite];

}


빌드앤런 해보자. 그런데 이전과 마찬가지로 실제 사냥이 되지는 않을 것이다. 당연하다. 충돌 검사 코드가 아직 작성되지 않았다. 다음 update: 메소드를 MammothHuntingScene.m에 추가해야 한다.

- (void)update:(ccTime)dt {

NSMutableArray *projectilesToDelete = [[NSMutableArray allocinit];

for (CCSprite *projectile in _projectiles) {

CGRect projectileRect = CGRectMake(

   projectile.position.x - (projectile.contentSize.width/2), 

   projectile.position.y - (projectile.contentSize.height/2), 

   projectile.contentSize.width

   projectile.contentSize.height);

NSMutableArray *targetsToDelete = [[NSMutableArray allocinit];

for (CCSprite *target in _targets) {

CGRect targetRect = CGRectMake(

   target.position.x - (target.contentSize.width/2), 

   target.position.y - (target.contentSize.height/2), 

   target.contentSize.width

   target.contentSize.height);

if (CGRectIntersectsRect(projectileRect, targetRect)) {

[targetsToDelete addObject:target];

}

}

for (CCSprite *target in targetsToDelete) {

[_targets removeObject:target];

[self removeChild:target cleanup:YES];

}

if (targetsToDelete.count > 0) {

[projectilesToDelete addObject:projectile];

}

[targetsToDelete release];

}

for (CCSprite *projectile in projectilesToDelete) {

[_projectiles removeObject:projectile];

[self removeChild:projectile cleanup:YES];

}

[projectilesToDelete release];

}


update: 메소드는 루프를 돌면서 총알과 타겟이 교차하는 지  CGRectIntersectsRect를 통해 검사한다. 만약 교차한다면 화면과 배열에서 제거해 버린다. 
여기서 한 가지 주의할 것이 있다. 충돌 검사 루프를 하는 동안은 배열의 객체를 삭제할 수 없으므로 제거 대상을 배열에 넣었다가 삭제한다.

정말 마지막으로 다음의 코드를 init 메소드 하단에 추가하고 빌드앤런하자. 이제 맘모스를 사냥할 수 있을 것이다.

[self schedule:@selector(update:)];


서두에 간단히 물리 엔진에 대해서 이야기 했던 것처럼, 간단한 그래픽 기반 게임 조차 어느 정도 물리 코드가 필요하다. 이 말은 충돌 검사의 다양한 방법이 있다는 것이다. 다음 기회에 Cocos2D의 물리 엔진인 Box2d와 Chipmunk를 공부할 항목에 추가해야 겠다.

---

Posted by windship
프로그래밍/Cocos2D2010. 7. 2. 17:54
MammothHunting은 우에서 좌로 움직이는 맘모스를 잡는 게임이다. 이제 타겟인 맘모스와 맘모스를 사냥하기 위한 총알 발사에 관해 살펴 보자.

타겟 추가
타겟은 화면 우측의 랜덤한 Y축을 기준으로 화면 좌측으로 이동한다. MammothHuntingScene.m에 다음의 addTarget 메소드를 init 메소드 이전에 추가한다.

-(void)addTarget {

CCSprite *target = [CCSprite spriteWithFile:@"target.png" rect:CGRectMake(007070)]; 

// 타겟(맘모스) Y 위치 결정.

CGSize winSize = [[CCDirector sharedDirectorwinSize];

int minY = target.contentSize.height/2;

int maxY = winSize.height - target.contentSize.height/2;

int rangeY = maxY - minY;

int actualY = (arc4random() % rangeY) + minY;

// 오른쪽  화면 밖에 X 위치와 위에서 결정한 Y축으로 타켓의 포지션 결정.

target.position = ccp(winSize.width + (target.contentSize.width/2), actualY);

[self addChild:target];

// 타겟의 속도 결정.

int minDuration = 2.0;

int maxDuration = 4.0;

int rangeDuration = maxDuration - minDuration;

int actualDuration = (arc4random() % rangeDuration) + minDuration;

// 액션 생성.

id actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp(-target.contentSize.width/2, actualY)];

id actionMoveDone = [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)];

[target runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];

}


위 메소드 중 새로 등장하는 액션에 대해서 잠깐 정리하고 넘어가자. Cocos2D는 다양한 액션을 제공한다. 액션 클래스는 CocosNode 클래스를 상속한 클래스로 애니메이션 처리를 위한 클래이다. 대부분 스프라이트 클래스의 애내메이션 처리를 위해 사용된다.

액션 클래스를 이용한 애니메이션은 시간과 동작에 따라 나눌 수 있다. 우선 시간에 따라서는 다음과 같에 세 가지로 구분된다.

InstanceAction: 한 번만 실행된다.
IntervalAction: 주어진 시간 동안 같은 동작을 반복해서 실행한다.
RepeatAction: 일정한 시간 동안 일정한 패턴을 반복한다.

동작에 따른 구분은 다음과 같다.

Transformation Action: Move, Rotate, Scale, Jump 등.
Composable Action: Sequence, Spawn, Repeat, Revers.
Ease Action: Exp, Sin, Cubic 등.
Etc Action: CallFun, Orbit.

Transformation Action은 스프라이트의 이동, 회전 등과 같은 움직임의 일회성 처리나 효과 처리를 위한 액션이다. Composable Action은 Transformation Action 여러 개가 순차적으로 또든 동시에  애니메이션의 실행이 필요한 경우 사용한다. Ease Action은 애니메이션을 구현하는 속도와 관련된 액션들이다. Transformation Action과 Composable Action이 기본 속도 1로 동작한다면, Ease Action은 애니메이션을 점점 빠르게 하거나, 갑자기 빠르게 바꾸는 것과 같은 처리를 할 수 있다. 그 외에 콜백 메소드를 호출해서 실행하는 CallFunc나 궤도를 따라 움직이도록 할 수 있는 Orbit과 같은 액션들이 있다. CallFunc의 콜백 기능을 이용하면 애니메이션이 끝난 다음에 액션을 발생시키는 등의 처리를 쉽게 할 수 있다.

다음은 액션을 생성한 부분의 CCCallFuncN에 생성한 콜백 함수인 spriteMoveFinished 메소드를 addTarget 메소드 이전에 추가해야 한다.

-(void)spriteMoveFinished:(id)sender {

CCSprite *sprite = (CCSprite *)sender;

[self removeChild:sprite cleanup:YES];

}


spriteMoveFinished 메소드는 화면 밖으로 사라진 스프라이트를 제거하는데  사용된다. 메모리 릭과 관련하여 더 좋은 방법을 선택해야 하지만, 간단한 게임이므로 생략한다.

마지막으로, 실제 타겟을 추가하기 위해 다음 구문을 init 메소드 마지막(if 구문의...)에 추가하고, 실제 gameLogic 메소드를 addTarget 메소드 다음에 구현한다.

[self schedule:@selector(gameLogic:) interval:1.0];


-(void)gameLogic:(ccTime)dt {

[self addTarget];

}


다시 빌드앤런 하면 다음과 같이 맘모스가 우에서 좌를 이동하는 화면을 볼 수 있다.




총알 발사
실제 맘모스를 잡기위해서는 꽤 묵직한 총과 총알이 필요하겠지만, 간단한 예제 이므로, 맘모스는 케익을 던져 잡는 것으로 처리하기로 한다. 물론 케익은 고릴라가 던질 것이다.

케익을 던지는 방법은 여러 가지가 있겠지만, MammothHunting 게임에서는 화면을 터치하는 방식을 선택했다. 총알 발사 처리를 위해서는 간단한 수학 공식이 필요하다. 우선 다음 그림을 보자.


총알 발사 처리를 위해 CCMoveTo 액션을 사용할 것이다. CCMoveTo 액션의 API를 보면 액션 지정을 위해 시간과 거리가 필요하다. 플레이어(고릴라)가 발사(터치)한 총알(케익)까지의 거리를 계산하기 위해 먼저 한 가지 사실을 가정해야 한다. 발사된 총알은 터치한 지점을 지나 화면 밖까지 이동한다는 점이다. 이 내용을 기초로 그림을 그려 본 것이 위의 그림이다. 그림의 내용을 상기하면서 다음 단계로 넘어 가자. 작은 직삼각형과 같은 비율의 큰 직삼각형을 기억하라.

우선 터치로 총알을 발사하기 위해 다음 코드를 init 메소드(if 구문 상단에...)에 추가하자. 레이어에 터치 이벤트를 활성화 한 것이다.

self.isTouchEnabled = YES;


그리고 터치 이벤트를 처리하기 위한 콜백 메소드(ccTouchesEnded)를 다음과 같이 구현한다.

-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

// 처리를 위한 터치 선택.

UITouch *touch = [touches anyObject];

CGPoint location = [touch locationInView:[touch view]];

location = [[CCDirector sharedDirectorconvertToGL:location];

// 총알(케익) 초기 위치 설정.

CGSize winSize = [[CCDirector sharedDirectorwinSize];

CCSprite *projectile = [CCSprite spriteWithFile:@"projectile.png" rect:CGRectMake(004040)];

projectile.position = ccp(20, winSize.height/2);

// 총알 위치의 오프셋(offset) 측정.

int offX = location.x - projectile.position.x;

int offY = location.y - projectile.position.y;

// 총알 발사를 / 수직이나 뒤로  경우 탈출함(발사되지 않음).

if (offX <= 0return;

// 포지션 이중 체크.

[self addChild:projectile];

// 총알을 발사할 위치 측정.

int realX = winSize.width + (projectile.contentSize.width/2);

float ratio = (float) offY / (float) offX;

int realY = (realX * ratio) + projectile.position.y;

CGPoint realDest = ccp(realX, realY);

// Determine the length of how far we're shooting

// 발사한 총알의 길이(궤적) 얼마인지 측정.

int offRealX = realX - projectile.position.x;

int offRealY = realY - projectile.position.y;

float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));

float velocity = 480/1// 480pixels/1sec

float realMoveDuration = length/velocity;

// 총알을 종점(endpoint, 화면 ...)까지 이동 시킴.

[projectile runAction:[CCSequence actions:

   [CCMoveTo actionWithDuration:realMoveDuration position:realDest],

   [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)], 

   nil]];

}


ccTouchesEnded 메소드의 내용의 첫 번째 부분은 처리할 하나의 터치를 선택하여 현재 뷰에서 위치(location)을 확인한다. 그리고 현재 화면 모드가 landscape이므로 convertToGL 메소드로 좌표를 변환한다. 이 것은 이전 포스트에서 언급했던 것으로 주의해야 할 부분이다. 왜냐하면, UIKit(Quartz)의 좌표계 Top-Left(좌상단)가 원점이 되는 좌표계를 쓰지만, Cocos2d는 OpenGL 기반이라 좌하단이 원점이 되는 좌표계를 사용하기 때문이다. 그래서 위치를 계산하기 전에 convertToGL 메소드로 좌표들을 꼭 변환해야 한다.

다음은 총알(케익) 스프라이트를 초기화 한다. 그리고 위에서 언급했던 수학으로 이동 시킬 총알의 위치를 계산해야 한다. 여기서 필요한 것이 바로 피타고라스의 정리이다.

임의의 직각삼각형에서 빗변을 한 변으로 하는 정사각형의 넓이는 다른 두 변을 각각 한 변으로 하는 정사각형의 넓이의 합과 같다.

물론 이 알고리즘은 총알을 발사하는 방법 중 가장 간단한 것의 하나일 뿐이다. 본 예제는 아주 단순한 경우이므로 이정도로 만족하고 넘어간다.

여기에서 중요한 또 한 가지의 내용은, 총알이 발사되는 시간과 속도의 관계를 이해하는 것이다. 학창 시절 배운 물리 법칙과 몇 가지 사항을 기억해 내야 한다.
  • 거리(distance)는 스칼라(scalar) 이고 변위(displacement)는 벡터(vector) 이다.
  • 벡터는 화살표를 사용해 나타낼 수 있다.
  • 거리 = 속도 * 시간
게임 개발을 하려면 기본적으로 수학과 물리학이 필수이다. 그렇다고 다시 교과서를 꺼내 볼 수도 없고... 이 문제를 해결하기 위해 다음 두 권의 책을 추천한다. 한 권은 마침 번역이 되어 있다.

어쨌든 알고리즘 같지 않은 알고리즘을 적용한 것이 액션을 추가하기 전까지의 소스 내용이다. 마지막으로 총알에 액션을 추가하고, 빌드앤런 하자. 이제 화면을 터치하면 총알이 발사될 것이다.


다음 포스트에서는 게임 개발 시 가장 중요한 부분인 충돌 검사에 대해 다룰 것이다. 두 번째 포스트를 마무리하기 전에 [MammothHunting] - Cocos2D를 사용한 아이폰 게임 개발 튜토리얼 1 에서 언급했던 Director, Scene, Layer, Sprite에 대해 정리해 보자.

Cocos2D의 모든 클래스는 CocosNode를 상속한다. CocosNode 클래스는 위치, 색상, 투명도, 크기, 회전, 가시성 여부, 카메라, 그리드 속성을 가지고 있으며 이들 속성을 필요에 따라 변경이 가능하다. CocosNode의 또 다른 특징은 자식 노드를 가질 수 있다는 점이다. 이는 객체들의 계층 구조를 만들 수 있음을 의미한다. 계층 구조가 가능함으로써 부모의 속성이 변할 때 자식의 속성까지 쉽게 변경할 수 있다.

Cocos2D에서 게임 화면을 구성하기 위해 제공되는 네 개의 클래스가 바로 CocosNode를 상속한 Director, Scene, Layer, Sprite 이다.

Director
Director는 싱글톤 클래스이다. Cocos2D는 게임의 화면을 Scene이라는 장면 단위로 구성하여 처리할 수 있는 기능을 제공한다. Director는 바로 이들 장면을 관리한다.

Scene
Scene은 게임에서 하나의 장면을 표현한다. 영화의 장면과 유사하다. 게임 내에서 장면 변경이 필요한 경우, 생성하여 각 장면마다 장면의 Scene 클래스로 처리한다.

Layer
Layer는 Sprite와 함께 Scene을 구성한다. Scene과 Layer의 가장 큰 차이점은, Scene은 사용자의 처치 이벤트를 받아서 처리할 수 없지만 Layer는 TouchEventsDelegate 프로토콜로 사용자의 처치 이벤트를 처리할 수 있다는 점이다. 다음 그림을 보면 이해하기 쉬울 것이다.


Sprite 
Director, Scene, Layer가 추상적이라면, Sprite는 실제로 화면에 출력되는 구체적 개념이다. Sprite는 컴퓨터 그래픽스에서 화면을 구성하는 정적인 이미지나 애니메이션이 되는 일련의 이미지를 가리킨다. 보통 게임에 사용되는 이미지라고 이해하면 된다. 일반적인 이미지와 구별되는 가장 큰 특징 중 하나는 투명색을 가진다는 점이다. 
실제 게임 개발 시 개발의 대부분의 시간이 Sprite를 생성하고 출력한 다음 사용자 입력이나, 기타 이벤트가 발생할 때마다 이들 이벤트에 대응하는 처리를 하는데 소요된다.

---

Posted by windship