안드로이드를 위한 cocos2d-x 개발 환경 구축
'Android > cocos2d-x' 카테고리의 다른 글
cocos2d-x (cocos2d c++버전) (0) | 2011.03.28 |
---|
cocos2d-x (cocos2d c++버전) (0) | 2011.03.28 |
---|
유니티에서 3D 캐릭터가 달리는 중에 상체만 공격애니메이션을 하게 만드려고 이리저리 삽질한 결과 정리..
[중요사항]
1. 우선 AnimationState.Layer가 같은 애니메이션은 Play(or CrossFade)하려는 애니메이션 외 모두를 정지시키면서 실행된다.
같이 실행되려면 레이어를 달리해야된다.
2. 그리고, Layer 디폴트는 0이고 Layer 숫자가 높을수록 애니메이션 우선순위가 높다.
애니메이션 두 개가 동시에 Play 될 때, Layer가 높은 애니메이션이 우선시 된다.
3. AddMixingTransform에 해당 애니메이션이 적용되는 부위(상체라던지..)의 Bone을 찾아서 넣어준다.
예) Transform mixTransform = transform.Find("Bip00 Spine");
animation["Attack"].AddMixingTransform(mixTransform);
[정리]
animation["RUN"].layer = 0;
animation["ATTACK"].layer = 1;
animation["ATTACK"].blendMode = AnimationBlendMode.Blend;
animation["ATTACK"].AddMixingTransform(trBone); // trBone은 알아서 받기..
- 이렇게 하고 RUN 중에 ATTACK 애니를 CrossFade 해주면 하체는 달리면서 상체는 공격하는 애니가 실행된다.
간단한 Nav Mesh 예제 (0) | 2012.11.21 |
---|---|
런타임 중 텍스쳐 교체 (0) | 2012.11.20 |
Unity3d Android 해상도 설정하기 ( Screen.SetResolution ) (0) | 2012.11.14 |
Unity remote 사용방법 (iPhone, Android) (0) | 2012.11.08 |
Smooth Follow Camera (0) | 2012.11.06 |
유니티의 애니메이션 시스템은 멋지게 애니메이션이 적용된 스키닝이 된 캐릭터를 만드는 것을 가능하게 해줍니다. 애니메이션 시스템은 애니메이션 블렌딩 blending, 믹싱 mixing, 가산 additive 애니메이션, 걷기사이클 시간동기화 walk cycle time synchronization, 애니메이션 레이어, 애니메이션 재생에 관한 모든 면에서의 제어 (시간, 속도, 블렌드 웨이트 blend-weights), 버텍스 당 1, 2, 혹은 4개 본 bone 들의 메쉬 스키닝뿐만 아니라 물리 기반의 랙돌 ragdoll 과 절차적 애니메이션 procedural animation 도 지원합니다. 가장 만족스런 결과를 얻기 위해서는Modeling Optimized Characters 페이지의 유니티에서 최적의 성능을 내는 리깅된 캐릭터 생성을 위한 가장 좋은 방법들과 기법들에 대해 읽어 보시는 것을 추천해 드립니다.
애니메이션 캐릭터를 만드는 것은 두 가지와 관련이 있습니다: 세상에서 그들이 움직이게 만들고 그에 따라 캐릭터가 애니메이션을 하게 만드는 것 입니다. 캐릭터를 움직이는 것에 대해 더 알고 싶으시다면 다음 Character Controller page를 참조하세요. 이 페이지는 애니메이션에 촛점을 맞추고 있습니다. 실제 캐릭터들의 애니메이션 적용은 유니티의 스크립팅 인터페이스를 통해 이루어 집니다.
여기에서 미리 셋업이 된 애니메이션 적용 캐릭터가 있는 example demos를 다운로드 받으세요. 이 페이지에서 기본을 익히면 animation script interface도 보시기 바랍니다.
원하신다면 이 페이지에서 다루는 아래의 주제들을 탐구해 보세요:
오늘날의 게임에서 애니메이션 블렌딩 blending 은 캐릭터의 부드러운 애니메이션을 위해서 필수적인 요소입니다. 애니메이션을 만드는 사람은 각각의 애니메이션을 만듭니다. 예를 들어, 걷고, 뛰고, 쉴 때 idle 동작, 또는 사격하는 애니메이션이 있습니다. 게임 중에서는 어떤 때라도 가만히 있는 idle 애니메이션에서 걷는 애니메이션 또는 반대로 바꿀 수가 있어야 합니다. 물론 사용자는 움직임이 갑작스레 튀지 않고 부드럽게 전환되는 애니메이션을 원할 것입니다.
이때 필요한 것이 바로 애니메이션 블렌딩입니다. 유니티에서는 하나의 같은 캐릭터에 어떤 수의 애니메이션도 넣을 수가 있습니다. 모든 애니메이션들은 최종 애니메이션을 만들기 위해 서로 섞여지거나 더해집니다.
사용자가 할 첫 스텝은 캐릭터를 가만히 쉬는 애니메이션에서 걷는 애니메이션으로 부드럽게 전환하는 것입니다. 스크립트 작성시 작업을 쉽게 하기위해 애니메이션의 랩 모드 Wrap Mode를 `반복 `Loop으로 설정합니다. 그리고 사용자가 만드는 스크립트가 애니메이션이 재생되는 유일한 것이 되게 자동으로 재생되게 Play Automatically`` 를 꺼놓습니다.
캐릭터를 동작시키기 위한 첫 스크립트는 꽤 간단한 편입니다. 사용자가 알아야 할 것은 단지 캐릭터가 얼마나 빨리 움직이는지 감지할 방법과 걷는 애니메이션과 쉬는 애니메이션 사이에 녹아들어 사라지게 합니다. 이 간단한 테스트를 위해서 미리 설정된 입력 축들 input axes 을 사용합니다.
function Update () { if (Input.GetAxis("Vertical") > 0.2) animation.CrossFade ("walk"); else animation.CrossFade ("idle"); }
이 스크립트를 실행하기 위해서는:
사용자가 재생 Play 버튼을 누르고, 위방향키를 누르고 있으면 캐릭터가 걷기 시작하고 손가락을 떼면 쉬는 포즈로 돌아갈 것입니다.
레이어는 사용자가 애니메이션을 그룹화하고 가중치 weighting 에 우선 순위를 매기는 것을 가능하게 해주는 굉장히 중요한 개념입니다.
유니티 애니메이션 시스템에서 사용자는 원하는 만큼 애니메이션 클립들을 서로 블렌드 할수 있습니다. 사용자는 수동으로 가중치를 주거나 간단히 자동으로 가중치를 주는 animation.CrossFade()를 이용할 수 있습니다.
걷는 것과 뛰는 애니메이션이 있고 둘 모두 1 (100%) 의 가중치를 가진다고 가정해 보세요. 유니티가 최종 애니메이션을 생성할 때 가중치를 정규화 합니다. 즉 걷는 것과 뛰는 애니메이션이 50%씩의 가중치를 각각 부여하게 되는 것입니다.
하지만 대부분의 경우 사용자는 두 개의 애니메이션이 있을 때 어떤 애니메이션이 더 가중치를 적용받을지 우선 순위를 정하길 원합니다. 물론 이때 모든 가중치의 합이 수동으로 100%가 되도록 할 수도 있겠지만 레이어들을 사용하는 것이 훨씬 쉬운 방법이 됩니다.
한 예로써 사용자가 사격하고, 쉬고, 그리고 걷는 애니메이션이 있다고 가정합니다. 걷는 애니메이션과 쉬는 애니메이션은 플레이어의 속도에 따라 블렌드되어 섞이겠지만, 사격 버튼이 눌러졌을때는 사격하는 애니메이션만이 나와야 합니다. 그러므로 사격 애니메이션은 이들 중 더 높은 우선 순위를 가지게 됩니다.
이것을 위한 가장 쉬운 방법은 사격 중에도 걷고 쉬는 애니메이션을 재생하게 하는 것입니다. 그러기 위해서는 사격 애니메이션을 쉬거나 걷기 보다 높은 레이어로 위치시켜야 하고 이것은 사격 애니메이션이 블렌드 가중치 blend weights 를 가장 우선 순위로 받게 된다는 뜻입니다. 걷기와 쉬기 애니메이션은 사격 애니메이션이 100% 블렌드 가중치를 다 사용하지 않을 때에만 가중치를 받게 됩니다. 그래서 크로스페이딩 CrossFading 으로 사격 애니메이션을 걷기나 쉬기 애니메이션 안에 들여보낼 때 사격 애니메이션의 가중치는 0에서 시작해 짧은 순간에 100%로 가중치가 변할 것입니다. 처음에 걷기와 정지레이어는 블렌드 가중치를 받지만 사격 애니메이션이 완전히 들어오면 (fade in) 가중치를 전혀 받을 수 없게 될 것입니다. 이것은 정확히 우리가 원하던 바입니다!
function Start () { // 모든 애니메이션을 룹으로 설정 animation.wrapMode = WrapMode.Loop; // 사격은 제외 animation["shoot"].wrapMode = WrapMode.Once; // 쉬기와 걷기를 아래 레이어로 놓습니다. (디폴트 레이어는 항상 0) // 이것은 두 가지를 할 것입니다. // - 사격과 쉬기/걷기는 다른 레이어에 있으므로, // 크로스페이드 CrossFade 를 호출할 때 그들은 서로의 재생에 영향을 끼치지 않을 것입니다.. // - 사격은 더 높은 레이어에 있으므로, // 페이드-인 (fade-in) 할때 그 애니메이션은 쉬기/걷기 애니메이션을 대체할 것입니다. animation["shoot"].layer = 1; // 이미 재생되는 애니메이션을 정지시킵니다.Stop animations that are already playing //(사용자가 자동재생을 비활성화 시켰을 경우) animation.Stop(); } function Update () { // 눌려진 키에 따라서, // 걷기나 쉬기 애니메이션을 재생합니다. if (Mathf.Abs(Input.GetAxis("Vertical")) > 0.1) animation.CrossFade("walk"); else animation.CrossFade("idle"); // Shoot if (Input.GetButtonDown ("Fire1")) animation.CrossFade("shoot"); }
기본 설정으로 animation.Play()나 animation.CrossFade() 는 같은 레이어에 있는 애니메이션을 정지시키거나 사라지게 fade-out 합니다. 이것이 대부분의 경우 이것은 사용자가 원하는 바입니다. 위의 우리의 예제에서는 쉬기/걷기는 사격 애니매이션에 영향을 주지 않으며 그 반대의 경우도 마찬가지 입니다 (원한다면animation.CrossFade의 옵션 파라미터에서 이 것을 바꿀 수 있습니다).
애니메이션 믹싱은 애니메이션을 몸의 어떤 일 부분에만 적용시켜 게임에서 생성해야 하는 애니메이션의 수를 줄여줍니다. 이것은 그런 애니메이션들이 다른 애니메이션들과 여러 조합으로 이루어져 사용될 수 있다는 뜻입니다.
사용자는 주어진 애니메이션상태 AnimatioState 에 AddMixingTransform()을 호출하여 애니메이션 믹싱 트랜스폼 animation mixing transform 을 다른 애니메이션에 더합니다.
믹싱의 한 예로는 손을 흔드는 애니메이션이 있습니다. 사용자는 캐릭터가 쉬거나 걸을 때 이 손 흔드는 애니메이션을 사용하길 원합니다. 애니메이션 믹싱이 없다면 사용자는 걸으며 손 흔드는 것 쉬면서 손흔드는 두개의 애니메이션을 만들어야 할 것입니다. 그러나 만약 사용자가 손 흔드는 애니메이션에 어깨 트랜스폼을 믹싱 트랜스폼으로써 추가하면, 손 흔드는 애니메이션은 어깨 조인트에서 손까지는 완전히 제어할 수 있게 될 것입니다. 나머지 몸은 그것에 의해 영향을 받지 않기 때문에 캐릭터는 쉬거나 걷는 애니메이션을 계속할 것입니다. 그러므로 몸의 나머지 부분이 쉬거나 걷는 동안에 손이 흔들리게 하려면 위와 같은 단 하나의 애니메이션 만이 필요하게 됩니다.
/// 트랜스폼 변수를 사용하여 믹싱 트랜스폼을 더합니다. var shoulder : Transform; animation["wave_hand"].AddMixingTransform(shoulder);
경로 path 를 이용한 또 다른 예.
function Start () { // 경로를 이용하여 믹싱 트랜스폼을 더합니다. var mixTransform : Transform = transform.Find("root/upper_body/left_shoulder"); animation["wave_hand"].AddMixingTransform(mixTransform); }
부가 애니메이션 Additive animation 과 애니메이션 믹싱은 게임을 위해 생성해야 하는 애니메이션 수를 줄이는 것을 줄이고, 얼굴표정 facial 애니메이션을 만드는데 중요한 역할을 합니다.
사용자가 걷거나 뛰면서 방향을 돌때 옆으로 기우는 캐릭터를 만든다고 가정해 봅시다. 이 경우 각각 애니메이션이 필요한 다음과 같은 네가지 조합이 필요하게 됩니다: 왼쪽으로 기울며 걷기 walk-lean-left, 오른쪽으로 기울며 걷기 walk-lean-right, 왼쪽으로 기울며 뛰기 run-lean-left, 오른쪽으로 기울며 뛰기 run-lean-right. 각 조합을 위해 각각 별개의 애니메이션을 만드는 것은 이와 같은 간단한 경우에도 훨씬 더 많은 추가 작업을 시키게 되고, 다른 액션들이 추가될 때마다 조합의 수는 기하급수적으로 늘어날 것입니다. 다행히도 가산 애니메이션 additive animation 과 믹싱은 단순 움직임들의 조합을 위해 각각 따로 애니메이션을 만들어야 하는 번잡함을 줄여 줍니다.
가산 애니메이션은 한 애니메이션의 효과를 재생중인 다른 애니메이션들 위에 겹치게 overlay 합니다. 가산 애니메이션을 만들때 유니티는 애니메이션 클립의 첫번째 프레임과 현재 프레임의 차이를 계산할 것입니다. 그리고 이 차이를 다른 모든 재생 중인 애니메이션 위에 적용합니다.
다시 아까의 예로 돌아가서 이제 사용자는 왼쪽이나 오른쪽으로 기운 애니메이션을 만들면 유니티는 이것을 걷거나 쉬거나 뛰는 동작의 애니메이션 위에 상위 레이어로 놓을 수 있습니다.
이것을 만들기 위한 코드입니다:
private var leanLeft : AnimationState; private var leanRight : AnimationState; function Start () { leanLeft = animation["leanLeft"]; leanRight = animation["leanRight"]; //기우는 애니메이션 lean animation 을 별개의 레이어에 놓아서, //CrossFade 를 호출하는 다른 것들이 그것에 영향을 주지 않도록 합니다. leanLeft.layer = 10; leanRight.layer = 10; //기우는 애니메이션을 가산 additive 으로 설정합니다. leanLeft.blendMode = AnimationBlendMode.Additive; leanRight.blendMode = AnimationBlendMode.Additive; //기우는 애니메이션을 ClampForever 로 설정합니다. //ClampForever가 설정되면 애니메이션은 클립의 끝에 도달해도 //자동적으로 정지되지 않습니다. leanLeft.wrapMode = WrapMode.ClampForever; leanRight.wrapMode = WrapMode.ClampForever; //애니메이션을 활성화 Enable 하고 그것을 완전히 들어오게 fade in 합니다. //여기서 사용자는 animation.Play 를 사용하지 않습니다. 왜냐하면 //그것은 업데이트 함수에서 시간을 수동적으로 조절할 수 있기 때문입니다. //대신에 사용자는 그냥 애니메이션을 활성화시키고, 그것을 최대 가중치 weight 로 설정합니다. leanRight.enabled = true; leanLeft.enabled = true; leanRight.weight = 1.0; leanLeft.weight = 1.0; //테스트를 위해서 걷기 애니메이션을 재생한 후 반복 Loop 을 시킵니다. animation["walk"].wrapMode = WrapMode.Loop; animation.Play("walk"); } //모든 프레임을 정규화된 normalized 시간으로 설정합니다. //사용자가 적용시키기 원하는 정도에 따라서 말입니다. function Update () { var lean = Input.GetAxis("Horizontal"); // normalizedTime 은 첫 프레임때는 0 이고 클립의 마지막 프레임에는 1 이 됩니다. leanLeft.normalizedTime = -lean; leanRight.normalizedTime = lean; }
팁: 가산 애니메이션을 사용할 때 사용자가 가산 애니메이션과 함께 사용된 모든 트랜스폼 위에 가산이 아닌 다른 일반 애니메이션들도 같이 재생하는 것은 굉장히 중요합니다. 그렇지 않으면 그 애니메이션들은 마지막 프레임의 결과 위에 추가되 얹혀질 것입니다. 이것은 확실히 사용자가 원하는 바가 아닐 것입니다.
사용자는 가끔 캐릭터의 본 bone 을 순차적으로 애니메이션 만들기를 원합니다. 예를 들어, 사용자는 3D공간에서 캐릭터의 머리가 목표점을 추적하는 스크립트에 의해 조정되어 어느 한 점을 바라보기를 원합니다. 다행히도 유니티에서 본 bone 들은 그 스킨된 메쉬를 움직이는 트랜스폼일 뿐이기에 유니티는 이것을 매우 쉽게 할 수 있습니다. 사용자는 스크립트를 써서 어떤 게임오브젝트의 트랜스폼을 다루듯 캐릭터의 본들도 제어할 수 있는 것입니다.
한가지 중요하게 알아야 할 것은 애니메이션 시스템은 Update() 함수 후에 그리고 LateUpdate() 함수 전에 트랜스폼들을 업데이트 한다는 것입니다. 그러므로 만약 사용자가LookAt() 함수를 사용하고 싶다면 사용자는 사용자가 정말로 애니메이션을 오버라이딩 override 하는 것을 확실히 하기 위해 그 함수를 LateUpdate() 안에서 해야만 합니다.
랙돌 Ragdoll 들도 같은 방법으로 생성됩니다. 사용자는 단지 강체 Rigidbodies, 캐릭터 조인트 Character Joints 그리고 캡슐 충돌체 Capsule Colliders 들을 다른 본들에 붙여주기만 하면 됩니다. 그럼 이것은 사용자의 스킨된 캐릭터를 물리적으로 애니메이션화 하게 됩니다.
이 섹션은 유니티에서 애니메이션들이 엔진에 의해 재생될 때 어떻게 샘플링이 되는지 설명해 줍니다.
애니메이션 클립 AnimationClips 들은 보통 고정된 프레임 비율로 만들어 집니다. 예를 들어, 사용자는 3D 맥스나 마야에서 프레임 속도 60fps (frames per second) 의 애니메이션을 만들수 있습니다. 유니티에서 그 애니메이션을 불러올 때 이 프레임 속도는 임포터 importer 에 의해 읽히게 되어 불려온 애니메이션의 데이타도 60 fps로 샘플링 됩니다.
하지만 게임은 일반적으로 다양한 프레임 속도로 실행됩니다. 프레임 속도는 어떤 컴퓨터에서는 다른 컴퓨터에서보다 더 높을 수 있고, 특정 순간에 카메라가 보고 있는 뷰의 복잡도에 따라 1초부터 수 초까지 또 다양한 결과를 냅니다. 기본적으로 이것은 게임이 실행되고 있는 프레임의 정확한 속도에 관해 우리는 어떤 가정도 만들수 없다는 것을 의미합니다. 즉 애니메이션이 60 fps로 만들어 졌어도 이것은 56.72 fps나 83.14 fps 혹은 어떤 다른 fps 속도로도 재생될지 모르는 것입니다.
그래서 그 결과로, 유니티는 애니메이션을 원래 지정되어진 프레임 속도가 아닌 이런 다양한 속도로 샘플링을 하게 됩니다. 다행히도 3D 컴퓨터 그래픽 애니메이션은 개별적으로 분리된 프레임이 아닌 연속적인 커브 curve 로 이루어져 있습니다. 이런 커브들은 원래의 애니메이션의 프레임들의 시간들에 딱딱 들어맞는 것이 아니라 어떤 시간에나 샘플링 될 수 있게 되어 있습니다. 그래서 실제적으로는 만약 게임이 만들어진 것보다 더 높은 프레임 속도로 실행이 될 경우 애니메이션은 애니메이션이 제작된 소프트웨어에서보다 더 부드럽고 유연하게 보일 것입니다.
가장 실제적인 상황에서는 사용자는 유니티가 애니메이션을 다양한 프레임 속도 variable fps 로 샘플을 한다는 것은 무시해도 좋습니다. 하지만 만약 사용자가 트랜스폼이나 속성을 어떤 아주 특정한 설정으로 애니메이트하는 애니메이션들에 의지하는 게임플레이 로직을 가지고 있다면, 사용자는 재샘플링 resampling 이 보이지 않는 곳에서 일어난다는 것을 알고 있어야 합니다. 예를 들어, 만약 사용자가 30프레임에 걸쳐 객체가 0에서 180도로 회전하는 애니메이션을 가지고 있고 그리고 그것이 언제 중간까지 도달했는지 코드에서 알고 싶다면, 사용자는 현재 회전 각도가 90도인지 체크하는 조건문을 만들어서 확인하면 안됩니다. 유니티는 애니메이션을 게임속의 다양한 프레임 속도로 샘플링을 하므로, 회전이 90도가 조금 안될때 샘플링을 하고 나중에는 90도 바로 이후에 할지도 모릅니다. 만약 사용자가 애니메이션에서 어떤 특정 점에 다다랐을 때 알림 받기를 원한다면 AnimationEvent를 이용해야 합니다.
또한 다양한 프레임 속도의 샘플링의 결과로 WrapMode.Once 를 사용한 애니메이션이 재생될 때 정확히 마지막 프레임에서 샘플링되지 않을 수도 있다는 것에 주의하세요. 게임의 한 프레임에서는 애니메이션의 마지막 바로 전에 샘플되고, 다음 프레임에서는 시간이 애니메이션의 길이를 초과해 버려 비활성화되고 더 이상 샘플링이 되지 않을지도 모릅니다. 만약 마지막 프레임이 반드시 샘플링이 되게 해야한다면WrapMode.ClampForever를 사용해야 합니다. 이 경우 사용자가 애니메이션을 직접 멈출때까지 마지막 프레임 샘플링을 계속 할 것입니다.
출처 : http://unitykoreawiki.com/index.php?n=KrMain.AnimationScripting
특정시간 뒤에 함수 호출하도록 설정하기 (WaitForSeconds) (0) | 2012.11.21 |
---|---|
AnimationEvent 추가 방법 (0) | 2012.11.21 |
WaitForSeconds() (0) | 2012.11.21 |
Attributes 설명 모음 (0) | 2012.11.18 |
FadeInOut (0) | 2012.10.31 |
유니티로 게임을 만들었는데
기기마다 해상도의 가로세로 비율이 다 제각각 이여서 개발자 입장에서는 각각의 해상도마다 수정작업을 하러면 좀 짜증이 난다.
뭔가 방법이 있을거 같아서 검색해봤더니 답이 있었다.
Screen.SetResolution 라는 메소드를 사용하면 대부분의 안드로이드 단말기의 해상도에 맞춰 게임화면을 변경할 수 있다.
Screen.SetResolution( 원하는 해상도 width값, 원하는 해상도 height값, 풀스크린 여부 );
나의 경우는 아이폰 해상도 비율인 2:3에 맞춰서 작업을 해두었기 때문에
안드로이드에서도 2:3 비율로 작업한 이미지들이 짤리지 않게 하기 위해 게임이 실행하는 단말기의 스크린값을 아래와 같이 수정하였다.
Screen.SetResolution(Screen.width, (Screen.width/2)*3, true); // 2:3 screen rate
겔럭시S 의 경우 해상도가 480 x 800 인데 위와 같이 입력해주면 의도했던 모든 UI가 짤림 없이 화면에 다 보여진다.
단점으로는 이미지가 단말기의 해상도 비율에 따라 UI가 길죽하게 보여지거나 넓직하게 보여지게 된다.
( 16:9 비율의 디지털TV 에서 4:3 비율의 케이블 방송을 보는것과 같은 현상)
※ 주의 : SetResolution 메소드가 동작하지 않는 단말기도 있다고 한다.
런타임 중 텍스쳐 교체 (0) | 2012.11.20 |
---|---|
상체 애니메이션 덧붙이기(AddMixingTransform) (2) | 2012.11.16 |
Unity remote 사용방법 (iPhone, Android) (0) | 2012.11.08 |
Smooth Follow Camera (0) | 2012.11.06 |
Timer()함수를 대신하는 Invoke()함수 (0) | 2012.11.05 |
Contents[hide] |
This is a Deterministic Finite State Machine framework based on chapter 3.1 of Game Programming Gems 1 by Eric Dybsend. Therea are two classes and two enums. Include them in your project, and follow the explanations to get the FSM working properly. There´s also a complete example script at the end of this page.
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; /** A Finite State Machine System based on Chapter 3.1 of Game Programming Gems 1 by Eric Dybsand Written by Roberto Cezar Bianchini, Jully 2010 How to use: 1. Place the lables for the transitions and the states of the Finite State System in the corresponding enums. 2. Write new class(es) inheriting from FSMState and fill each one with pairs (transition-state). These pairs represent the state S2 the FSMSystem should be if while being on state S1, a transition T is fired and state S1 has a transintion from it to S2. Remember this is a Deterministic FSM. You can´t have one transition leading to two different states. Method Reason is used to determine which transition should be fired. You can write the code to fire transitions in another place, and leave this method empty if you feel it´s more appropriate to your project. Method Act has the code to perform the actions the NPC is supposed do if it´s on this state. You can write the code for the actions in another place, and leave this method empyt if you feel it´s more appropriate to your project. 3. Create an instance of FSMSystem class and add the states to it. 4. Call Reason and Act (or whicheaver methods you have for firing transitions and making the NPCs behave in your game) from your Update or FixedUpdate methods. Assynchronous transitions from Unity Engine, like OnTriggerEnter, SendMessage, can also be used, just call the Method PerformTransition from your FSMSystem instance with the correct Transition when the event occurs. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /// <summary> /// Place the lables for the Transitions in this enum. /// Don´t change the first lable, NullTransition as FSMSystem class uses it. /// </summary> public enum Transition { NullTransition = 0, // Use this transition to represent a non-existint transition in your system } /// <summary> /// Place the lables for the States in this enum. /// Don´t change the first lable, NullTransition as FSMSystem class uses it. /// </summary> public enum StateID { NullStateID = 0, // Use this ID to represent a non-existing State in your system } /// <summary> /// This class represents the States in the Finite State System. /// Each state has a Dictionary with pairs (transition-state) showing /// which state the FSM should be if a transition is fired while this state /// is the current state. /// Method Reason is used to determine which transition should be fired . /// Method Act has the code to perform the actions the NPC is supposed do if it´s on this state. /// </summary> public abstract class FSMState { protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>(); protected StateID stateID; public StateID ID { get { return stateID; } } public void AddTransition(Transition trans, StateID id) { // Check if anyone of the args is invallid if (trans == Transition.NullTransition) { Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition"); return; } if (id == StateID.NullStateID) { Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID"); return; } // Since this is a Deterministc FSM, // check if the current transition was already inside the map if (map.ContainsKey(trans)) { Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() + "Impossible to assign to another state"); return; } map.Add(trans, id); } /// <summary> /// This method deletes a pair transition-state from this state´s map. /// If the transition was not inside the state´s map, an ERROR message is printed. /// </summary> public void DeleteTransition(Transition trans) { // Check for NullTransition if (trans == Transition.NullTransition) { Debug.LogError("FSMState ERROR: NullTransition is not allowed"); return; } // Check if the pair is inside the map before deleting if (map.ContainsKey(trans)) { map.Remove(trans); return; } Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() + " was not on the state´s transition list"); } /// <summary> /// This method returns the new state the FSM should be if /// this state receives a transition and /// </summary> public StateID GetOutputState(Transition trans) { // Check if the map has this transition if (map.ContainsKey(trans)) { return map[trans]; } return StateID.NullStateID; } /// <summary> /// This method is used to set up the State condition before entering it. /// It is called automatically by the FSMSystem class before assigning it /// to the current state. /// </summary> public virtual void DoBeforeEntering() { } /// <summary> /// This method is used to make anything necessary, as reseting variables /// before the FSMSystem changes to another one. It is called automatically /// by the FSMSystem before changing to a new state. /// </summary> public virtual void DoBeforeLeaving() { } /// <summary> /// This method decides if the state should transition to another on its list /// NPC is a reference to the object that is controlled by this class /// </summary> public abstract void Reason(GameObject player, GameObject npc); /// <summary> /// This method controls the behavior of the NPC in the game World. /// Every action, movement or communication the NPC does should be placed here /// NPC is a reference to the object that is controlled by this class /// </summary> public abstract void Act(GameObject player, GameObject npc); } // class FSMState /// <summary> /// FSMSystem class represents the Finite State Machine class. /// It has a List with the States the NPC has and methods to add, /// delete a state, and to change the current state the Machine is on. /// </summary> public class FSMSystem { private List<FSMState> states; // The only way one can change the state of the FSM is by performing a trasintion // Don´t change the CurrentState directly private StateID currentStateID; public StateID CurrentStateID { get { return currentStateID; } } private FSMState currentState; public FSMState CurrentState { get { return currentState; } } public FSMSystem() { states = new List<FSMState>(); } /// <summary> /// This method places new states inside the FSM, /// or prints an ERROR message if the state was already inside the List. /// First state added is also the initial state. /// </summary> public void AddState(FSMState s) { // Check for Null reference before deleting if (s == null) { Debug.LogError("FSM ERROR: Null reference is not allowed"); } // First State inserted is also the Initial state, // the state the machine is in when the simulation begins if (states.Count == 0) { states.Add(s); currentState = s; currentStateID = s.ID; return; } // Add the state to the List if it´s not inside it foreach (FSMState state in states) { if (state.ID == s.ID) { Debug.LogError("FSM ERROR: Impossible to add state " + s.ID.ToString() + " because state has already been added"); return; } } states.Add(s); } /// <summary> /// This method delete a state from the FSM List if it exists, /// or prints an ERROR message if the state was not on the List. /// </summary> public void DeleteState(StateID id) { // Check for NullState before deleting if (id == StateID.NullStateID) { Debug.LogError("FSM ERROR: NullStateID is not allowed for a real state"); return; } // Search the List and delete the state if it´s inside it foreach (FSMState state in states) { if (state.ID == id) { states.Remove(state); return; } } Debug.LogError("FSM ERROR: Impossible to delete state " + id.ToString() + ". It was not on the list of states"); } /// <summary> /// This method tries to change the state the FSM is in based on /// the current state and the transition passed. If current state /// doesn´t have a target state for the transition passed, /// an ERROR message is printed. /// </summary> public void PerformTransition(Transition trans) { // Check for NullTransition before changing the current state if (trans == Transition.NullTransition) { Debug.LogError("FSM ERROR: NullTransition is not allowed for a real transition"); return; } // Check if the currentState has the transition passed as argument StateID id = currentState.GetOutputState(trans); if (id == StateID.NullStateID) { Debug.LogError("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " + " for transition " + trans.ToString()); return; } // Update the currentStateID and currentState currentStateID = id; foreach (FSMState state in states) { if (state.ID == currentStateID) { // Do the post processing of the state before setting the new one currentState.DoBeforeLeaving(); currentState = state; // Reset the state ot its desired contition before it can reason or act currentState.DoBeforeEntering(); break; } } } // PerformTransition() } //class FSMSystem
Here´s an example that implements the above framework. The gameobject with this script follows a path of waypoints and starts chasing a target if it comes within a certain distance from it. Attach this class to your NPC. Besides the framework transition and stateid enums, you also have to setup a reference to the waypoints and to the target (player). I used FixedUpdate() in the MonoBehaviour because the NPC reasoning schema doesn´t need to be called everyframe, but you can change that and use Update().
using System; using System.Collections.Generic; using System.Text; using UnityEngine; [RequireComponent(typeof(Rigidbody))] public class NPCControl : MonoBehaviour { public GameObject player; public Transform[] path; private FSMSystem fsm; public void SetTransition(Transition t) { fsm.PerformTransition(t); } public void Start() { MakeFSM(); } public void FixedUpdate() { fsm.CurrentState.Reason(player, gameObject); fsm.CurrentState.Act(player, gameObject); } // The NPC has two states: FollowPath and ChasePlayer // If it´s on the first state and SawPlayer transition is fired, it changes to ChasePlayer // If it´s on ChasePlayerState and LostPlayer transition is fired, it returns to FollowPath private void MakeFSM() { FollowPathState follow = new FollowPathState(path); follow.AddTransition(Transition.SawPlayer, StateID.ChasingPlayer); ChasePlayerState chase = new ChasePlayerState(); chase.AddTransition(Transition.LostPlayer, StateID.FollowingPath); fsm = new FSMSystem(); fsm.AddState(follow); fsm.AddState(chase); } } public class FollowPathState : FSMState { private int currentWayPoint; private Transform[] waypoints; public FollowPathState(Transform[] wp) { waypoints = wp; currentWayPoint = 0; stateID = StateID.FollowingPath; } public override void Reason(GameObject player, GameObject npc) { // If the Player passes less than 15 meters away in front of the NPC RaycastHit hit; if (Physics.Raycast(npc.transform.position, npc.transform.forward, out hit, 15F)) { if (hit.transform.gameObject.tag == "Player") npc.GetComponent<NPCControl>().SetTransition(Transition.SawPlayer); } } public override void Act(GameObject player, GameObject npc) { // Follow the path of waypoints // Find the direction of the current way point Vector3 vel = npc.rigidbody.velocity; Vector3 moveDir = waypoints[currentWayPoint].position - npc.transform.position; if (moveDir.magnitude < 1) { currentWayPoint++; if (currentWayPoint >= waypoints.Length) { currentWayPoint = 0; } } else { vel = moveDir.normalized * 10; // Rotate towards the waypoint npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation, Quaternion.LookRotation(moveDir), 5 * Time.deltaTime); npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0); } // Apply the Velocity npc.rigidbody.velocity = vel; } } // FollowPathState public class ChasePlayerState : FSMState { public ChasePlayerState() { stateID = StateID.ChasingPlayer; } public override void Reason(GameObject player, GameObject npc) { // If the player has gone 30 meters away from the NPC, fire LostPlayer transition if (Vector3.Distance(npc.transform.position, player.transform.position) >= 30) npc.GetComponent<NPCControl>().SetTransition(Transition.LostPlayer); } public override void Act(GameObject player, GameObject npc) { // Follow the path of waypoints // Find the direction of the player Vector3 vel = npc.rigidbody.velocity; Vector3 moveDir = player.transform.position - npc.transform.position; // Rotate towards the waypoint npc.transform.rotation = Quaternion.Slerp(npc.transform.rotation, Quaternion.LookRotation(moveDir), 5 * Time.deltaTime); npc.transform.eulerAngles = new Vector3(0, npc.transform.eulerAngles.y, 0); vel = moveDir.normalized * 10; // Apply the new Velocity npc.rigidbody.velocity = vel; } } // ChasePlayerState
유니티 4.2버전에 대한 Simple Observer Pattern (0) | 2013.12.02 |
---|
스크롤/드래그 이벤트를 받는 방법
[출처] [NGUI] UIDraggablePanel 사용법 정리|작성자 crazylulu
NGUI: Events(Event Functions) (2) | 2012.12.07 |
---|---|
NGUI Virtual Joystick (1) | 2012.12.05 |
유니티 NGUI 에서 라벨에 한글(폰트) 적용하기 (2) | 2012.11.05 |
NGUI - Sticky Floating Text (0) | 2012.10.28 |
NGUI: HUD Text (0) | 2012.10.26 |
상체 애니메이션 덧붙이기(AddMixingTransform) (2) | 2012.11.16 |
---|---|
Unity3d Android 해상도 설정하기 ( Screen.SetResolution ) (0) | 2012.11.14 |
Smooth Follow Camera (0) | 2012.11.06 |
Timer()함수를 대신하는 Invoke()함수 (0) | 2012.11.05 |
캐릭터 컨트롤 하기 (0) | 2012.11.03 |
Scrolling UVs (0) | 2012.11.08 |
---|---|
MeleeWeaponTrail (0) | 2012.10.11 |
A C# script that smoothly scrolls a material's UVs in an arbitrary direction/speed given by "Uv Animation Rate". Supports changing which material index and texture name, but the defaults will work with single material, single texture renderers.
using UnityEngine; using System.Collections; public class ScrollingUVs : MonoBehaviour { public int materialIndex = 0; public Vector2 uvAnimationRate = new Vector2( 1.0f, 0.0f ); public string textureName = "_MainTex"; Vector2 uvOffset = Vector2.zero; void LateUpdate() { uvOffset += ( uvAnimationRate * Time.deltaTime ); if( renderer.enabled ) { renderer.materials[ materialIndex ].SetTextureOffset( textureName, uvOffset ); } } }
파티클 한 번만 사용하고 종료하기 (0) | 2012.11.08 |
---|---|
MeleeWeaponTrail (0) | 2012.10.11 |
Toon/Tf2Shader (0) | 2013.07.19 |
---|---|
Toon/Basic with Alpha (0) | 2013.07.19 |
Toon/Lighted with Alpha (0) | 2013.07.19 |
스크립트로 Shader 변경 (0) | 2012.11.06 |
AlphaVertexColor (0) | 2012.11.04 |