글은 아래 블로그에 있습니다!!

 

https://prup.tistory.com/60

 

[JDBC] JAVA와 MSSQL 연동하기

# 필독 JDBC를 이용하여 JAVA와 MS-SQL을 연동하는 방법을 알려드리겠습니다. 설치를 하기 이전 당부의 말을 하나 드리자면, jdk의 버전과 ms-sql의 버전만 정확하기 아신다면! 설치를 못 할 이유가 없

prup.tistory.com

 

감사합니다~!!

# 상태패턴이란 ?



상태 패턴은 Event에 맞추어 상태를 객체 내부에서 바꾸어 주는 구조다.


GoF(Gang of Four)의 'Design Patterns'에서는 이를 "객체의 내부 상태에 따라 스스로 행동을 변경할 수 있게 허가하는 패턴으로, 이렇게 하면 객체는 마치 자신의 클래스를 바꾸는 것처럼 보입니다."라고 정의하였다.


게임에서 사용되는 상태 패턴을 이해하기 위해서는 유한 상태 기계(FSM, Finite State Machine)에서부터 시작해야 한다. 


먼저, 간단하게 FSM에 대해 설명해보자.

FSM은 컴퓨터 과학 분야 중의 하나인 오토마타 이론에서 나왔으며, 이러한 오토마타 분야 중에서 가장 간단한 축에 속한다.


FSM의 간단한 구조를 플로우 차트로 보자.


위의 그림이 상태 기계를 그린 플로우 차트다.


어떠한 특징을 가지고 있을까 ?

    • 가질 수 있는 '상태'가 한정된다 : 동작이 정해져 있다. 예를 들면, 서기/걷기/뛰기/공격으로 정의한다면 이 외의 상태는 존재하지 않는 셈이다.
    • 한 번에 '한 가지' 상태만 될 수 있다 : 기계를 가진 Object는 정의되어 있는 상태들 중 반드시 한 곳에만 속한다. 예를 들면, 걸으면서 공격을 할 수 없는 셈이다.
    • '입력'이나 '이벤트'가 기계에 전달된다 : 버튼을 누르거나, 키를 받거나하는 등의 상태 변화를 위한 조건이 존재하는 셈이다.
    • 각 상태에는 입력에 따라 다음 상태로 바뀌는 'Transition(전이)'가 있다 : 입력이 들어왔을 때에, 기존 상태에서 그 입력에 맞는 '전이'를 따라 다음 상태로 넘어간다.

물론, FSM이 위와 같은 다양한 장점 등을 가지고 있지만, 장점이 있다면 단점도 있다.

FSM은 '상태'와 '전이'로 관리하는 엄격하게 제한된 구조를 강제함으로써 많은 조건을 없앨 수 있지만, 그러한 상태가 수없이 많아지게 되면 결국 하드 코딩된 '전이'만이 존재하게 된다.


때문에, 인공지능 같이 복잡한 구조에 들어가게 되면 그 한계가 분명해지게 된다.


물론 위와 같은 한계를 극복하기 위한 몇가지 기술이 있다.

예를 들면, '병행 상태 기계', '계층형 상태 기계', '푸시다운 오토마타' 등이 존재한다.


위의 기술들에 관한 내용은 후에 시간이 되면 설명하겠다.



# 상태 패턴의 UML


위의 이미지는 GoF의 디자인 패턴 책에서 제공하는 UML이다.


 - Context

    • Client의 분야에 맞춘 인터페이스를 정의한다.
    • 현재 상태를 정의하는 Concrete State 서브 클래스의 인스턴스를 유지/관리 한다.
 - State
    • Context의 특정 상태와 관련된 (전이로 이어진) 행동을 캡슐화하기 위한 인터페이스를 정의한다.
 - Concrete State Sub Class
    • 각각의 서브 클래스는 Context의 상태(행동)과 관련된 상태(행동)을 구현한다.


# 상태 패턴의 시퀀스 다이어그램




# 캐릭터 조작에 사용한 UML


위는 실제 구현에 있어서 응용한 명령 패턴의 UML이다.



# 코드 - StateMachine.cs


객체의 행동을 결정해주는 기계다.

using UnityEngine;
public abstract class StateMachine : MonoBehaviour {
protected string state = "IDLE";
protected abstract void UpdateHandle(); // 각종 Event를 받음
protected abstract string UpdateState(); // Transition을 위한 조건을 검색하고, 해당 조건에 맞는 Transition을 받고 다음 상태로
protected void FixedUpdate() {
UpdateHandle();
state = UpdateState();
}
}
view raw StateMachine.cs hosted with ❤ by GitHub



# 코드 - StateClient.cs


실질적인 동작들이 이 곳에 들어가게 된다.

using UnityEngine;
/* 해당 클래스는 반드시 Rigidbody를 요구한다.
* 만약 해당 클래스를 지닌 객체에 Rigidbody가 없다면,
* 자동으로 Rigidbody를 추가한다.
*/
[RequireComponent(typeof(Rigidbody))]
public class StateClient : StateMachine {
// Component
public Rigidbody rb = null;
public Collider col = null;
// another object
private FireBall fireball = null;
private Transform skillStartPosition = null;
// meta value - float
public float moveSpeed = 3f;
public float jumpForce = 7f;
public float hitDistance = 0.35f; // 지면과의 거리 체크하여 지면에서 떨어져있는지 여부를 파악 (Raycast를 이용)
public LayerMask groundLayers;
// meta value - bool
public bool grounded = true; // 지면 접촉 여부
public bool canAttack = true; // 공격 가능 여부
public bool inputJump = true;
public bool inputAttack = true;
// related time
public float attackCoolDown = 2f;
public float attackCoolDownEnd = 0f;
public float attackCoolDownRemaining() { return Time.time >= attackCoolDownEnd ? 0 : attackCoolDownEnd - Time.time; }
public float attackCastTime = 0f;
public float attackCastTimeEnd = 0f;
public float attackCastTimeRemaining() { return Time.time >= attackCastTimeEnd ? 0 : attackCastTimeEnd - Time.time; }
// CallBack
private void Awake() {
rb = GetComponent<Rigidbody>();
col = GetComponent<Collider>();
if (col == null) Debug.LogError(name + "-- StateClient::Awake -- Collider doesn't exist.");
}
// Transition(전이)를 위한 조건
private bool EventCheckMove() { return IsMoving() == true; }
private bool EventCheckMoveEnd() { return IsMoving() == true && state == "MOVING"; }
private bool EventCheckJumpRequest() { return IsGround() == true && inputJump == true; }
private bool EventCheckIsJumping() { return IsGround() == false; }
private bool EventCheckJumpFinished() { return IsGround() == true && state == "JUMP"; }
private bool EventCheckAttackRequest() { return inputAttack == true && attackCoolDownRemaining() <= 0f; }
private bool EventCheckAttackCastingFinished() { return state == "ATTACK" && attackCastTimeRemaining() <= 0f; }
// 전이 조건 보조 함수
private bool IsMoving() {
return (Input.GetKey(KeyCode.W) ||
Input.GetKey(KeyCode.S) ||
Input.GetKey(KeyCode.A) ||
Input.GetKey(KeyCode.D));
}
private bool IsGround() {
if (grounded) hitDistance = 0.35f;
else hitDistance = 0.15f;
if (Physics.Raycast(transform.position - new Vector3(0f, 0.85f, 0f),
-transform.up,
hitDistance,
groundLayers)) {
return (grounded = true);
} else {
return (grounded = false);
}
}
// 각 상태에 대한 내부 구현 ( Concrete State 들... )
private string UpdateIDLE() {
if (EventCheckJumpRequest()) {
return "JUMP";
}
if (EventCheckIsJumping()) {
return "JUMP";
}
if (EventCheckAttackRequest()) {
attackCastTimeEnd = Time.time + attackCastTime;
return "ATTACK";
}
if (EventCheckMove()) {
return "MOVE";
}
if (EventCheckMoveEnd()) { } // 신경쓸 필요 x
if (EventCheckJumpFinished()) { } // 신경쓸 필요 x
if (EventCheckAttackCastingFinished()) { } // 신경쓸 필요 x
return "IDLE";
}
private string UpdateMOVE() {
if (EventCheckJumpRequest()) {
return "JUMP";
}
if (EventCheckIsJumping()) {
return "JUMP";
}
if (EventCheckAttackRequest()) {
attackCastTimeEnd = Time.time + attackCastTime;
return "ATTACK";
}
if (EventCheckMoveEnd()) {
return "IDLE";
}
if (EventCheckMove()) { }
if (EventCheckJumpFinished()) { }
if (EventCheckAttackCastingFinished()) { }
return "MOVE";
}
private string UpdateJUMP() {
if (EventCheckJumpFinished()) {
grounded = true;
return "IDLE";
}
if (EventCheckJumpRequest()) { }
if (EventCheckIsJumping()) { }
if (EventCheckAttackRequest()) { }
if (EventCheckMove()) { }
if (EventCheckMoveEnd()) { }
if (EventCheckAttackCastingFinished()) { }
return "JUMP";
}
private string UpdateATTACK() {
if (EventCheckAttackCastingFinished()) {
attackCoolDownEnd = Time.time + attackCoolDown;
CastAttack();
return "IDLE";
}
if (EventCheckJumpRequest()) { }
if (EventCheckIsJumping()) { }
if (EventCheckAttackRequest()) { }
if (EventCheckMove()) { }
if (EventCheckMoveEnd()) { }
if (EventCheckJumpFinished()) { }
return "ATTACK";
}
// state Machine 가상함수 내부 구현
protected override string UpdateState() {
if (state == "IDLE") return UpdateIDLE();
if (state == "MOVE") return UpdateMOVE();
if (state == "JUMP") return UpdateJUMP();
if (state == "ATTACK") return UpdateATTACK();
Debug.LogError(name + " -- StateClient::UpdateState -- No state named: " + state);
return null;
}
protected override void UpdateHandle() {
if (state == "IDLE" ||
state == "MOVE") {
MoveHandling();
JumpHandling();
AttackHandling();
} else
if (state == "JUMP") {
MoveHandling();
} else
if (state == "ATTACK") {
} else {
Debug.LogError(name + " -- StateClient::UpdateHandle -- No state named: " + state);
}
}
// Handling 함수
private void MoveHandling() {
if (Input.GetKey(KeyCode.W)) transform.position += moveSpeed * Time.fixedDeltaTime * transform.forward;
if (Input.GetKey(KeyCode.S)) transform.position += moveSpeed * Time.fixedDeltaTime * transform.forward * -1;
if (Input.GetKey(KeyCode.A)) transform.position += moveSpeed * Time.fixedDeltaTime * transform.right * -1;
if (Input.GetKey(KeyCode.D)) transform.position += moveSpeed * Time.fixedDeltaTime * transform.right;
}
private void JumpHandling() {
if (Input.GetKeyDown(KeyCode.Space)) {
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
inputJump = true;
}
}
private void AttackHandling() {
inputAttack = false;
if (Input.GetKeyDown(KeyCode.Mouse0)) {
inputAttack = true;
}
}
// 외의 함수
private void CastAttack() {
GameObject go = (GameObject)Instantiate(fireball.gameObject);
go.transform.position = skillStartPosition.position;
go.transform.rotation = skillStartPosition.rotation;
go.GetComponent<FireBall>().moveDirection = transform.forward;
}
}
view raw StateClient.cs hosted with ❤ by GitHub



# 구현한 코드의 그림 설명



그림을 통해 설명하면 위와 같은 구조이다.


위의 그림은 UpdateIDLE 함수의 내부가 어떻게 돌아가고 있는지에 대한 그림이다.

( 나머지 UpdateJUMP, UpdateATTACK, UpdateMOVE 함수들도 내부는 동일하다. )


EventCheck 관련 함수들은 Handler 함수들로부터 true / false를 만족한다.

그렇게 만족할 경우 해당 다음 상태로 넘어가는 구조이다.



# 글을 마치면서...


State.unitypackage


동작에 대한 정보는 첨부파일에 담겨있다.

해당 상태 패턴에 의해 조작되는 캐릭터를 보고 싶다면, 첨부파일을 받아서 실행시켜보자.



# 참고 자료

 1. GoF의 책 - 'Design Patterns'

 2. 로버트 나이스트롬의 책 - 'Game Programming Patterns'

 3. 위키백과의 '상태패턴' - https://goo.gl/gQ6QQK

'- Unity > 캐릭터 조작' 카테고리의 다른 글

Command Pattern (명령 패턴)  (0) 2018.08.30

# 명령패턴이란 ?


요청을 객체의 형태로 캡슐화하여 사용자가 보낸 요청을 원하는 시점에 이용할 수 있도록, 매서드 이름, 매개변수 등의 요청에 사용되는 정보를 로깅, 취소 할 수 있는 패턴이다.


GoF (Gang of Four)의 디자인 패턴 책에서는 "요청 자체를 캡슐화 하는 것입니다. 이를 통해 요청이 서로 다른 사용자(Client)를 매개변수로 만들고, 요청을 대기시키거나 로깅(Logging)하여 되돌릴 수 있는 연산을 지원합니다."라고 정의하였다.


명령 패턴에는 명령(Command), 수신자(Receiver), 발동자(Invoke), 클라이언트(Client)의 4가지의 용어가 따른다.



# 명령 패턴의 UML


위의 이미지는 GoF의 디자인 패턴 책에서 제공하는 UML이다.


 - Command 

    • 요청을 실행하기 위한 Interface를 선언한다.

 - ConcreteCommand  

    • receiver object와 action간의 바인딩을 정의한다.

    • receiver에서 대응하는 동작을 Invoking하여 실행시키는 것을 구현한다.

 - Client

    • Command에 요청을 수행하도록 요청한다.

- Receiver

    • 받은 요청을 어떻게 수행할지에 대한 세부 동작을 가지고 있다.



# 명령 패턴의 시퀀스 다이어그램


위의 이미지는 GoF의 디자인 패턴 책에서 제공하는 Sequence Diagram이다.



# 캐릭터 조작에 사용한 UML

위는 실제 구현에 있어서 응용한 명령 패턴의 UML이다.



# 코드 - Command.cs

요청을 받을시 그 요청을 실행하기 위한 명령을 만드는 가상클래스이다.

using UnityEngine;
public abstract class Command {
public abstract void execute(Actor actor);
}
view raw Command.cs hosted with ❤ by GitHub



# 코드 - DefineCommand.cs

요청을 받을시 그 요청을 대상에게 요청하여 실행한다.

using UnityEngine;
public class CommandIDLE : Command {
public override void execute(Actor actor) {
actor.IDLE();
}
}
public class CommandWALK : Command {
public override void execute(Actor actor) {
actor.WALK();
}
}
public class CommandJUMP : Command {
public override void execute(Actor actor) {
actor.JUMP();
}
}
public class CommandATTACK : Command {
public override void execute(Actor actor) {
actor.ATTACK();
}
}



# 코드 - Invoke.cs

이 곳에서 요청이 발생하고, 그 요청에 대응하는 명령을 호출한다.

using UnityEngine;
public class Invoke : MonoBehaviour {
public Actor actor;
private void Update() {
Command command = InputHandler(); // 요청 받기
if (command != null) command.execute(actor); // 요청 실행
}
private Command InputHandler() {
if (Input.GetKeyDown(KeyCode.Space))
return new CommandJUMP();
if (Input.GetKeyDown(KeyCode.Mouse0))
return new CommandATTACK();
if (Input.GetKey(KeyCode.W) ||
Input.GetKey(KeyCode.S) ||
Input.GetKey(KeyCode.A) ||
Input.GetKey(KeyCode.D))
return new CommandWALK();
return null;
}
}
view raw Invoke.cs hosted with ❤ by GitHub



# 코드 - Actor.cs

요청에 대한 동작을 지니고 있는 부모 객체다.

using UnityEngine;
/* 해당 클래스 컴포넌트는 반드시 Rigidbody를
지니고 있어야한다.
이 클래스를 컴포넌트로 넣을시,
해당객체에 Rigidbody가 없다면,
자동으로 Rigidbody를 추가한다.
해당객체에 Rigidbody가 있다면,
무시.
*/
[RequiredComponent(typeof(Rigidbody))]
public abstract class Actor : MonoBehaviour {
public abstract void IDLE();
public abstract void WALK();
public abstract void JUMP();
public abstract void ATTACK();
}
view raw Actor.cs hosted with ❤ by GitHub



# 코드 - Player.cs

실질적으로 요청이 있을시, 그 요청을 수행할 때, 그 요청이 적용되는 객체다.


using UnityEngine;
public class Player : Actor {
private Rigidbody rb = null;
private Collider col = null;
public float moveSpeed = 3f;
public float jumpForce = 7f;
public float hitDistance = 0.35f; // 캐릭터가 지면에 닿아있는지 체크하기 위한 거리
public LayerMask groundLayers; // 해당 Layer들만 지면으로 지정
[System.NonSerialized] public bool grounded = true; // 지면에 닿아있는지의 여부
[System.NonSerialized] public bool isCasting = false; // 공격을 시전중인지의 여부
[System.NonSerialized] public bool canAttack = true; // 공격이 가능한지의 여부
public float attackCooldown = 2f; // 해당 공격의 재사용까지 걸리는 시간
private float attackCooldownEnd = 0f; // 해당 공격을 재사용할 수 있게 되는 시간
private float attackCooldownRemaining() { return Time.time >= attackCooldownEnd ? 0 : attackCooldownEnd - Time.time; }
public float castingTime = 0.5f; // 해당 공격의 시전 시간
private float castingTimeEnd = 0f; // 해당 공격의 시전이 끝나는 시간
private float castingTimeRemaining() { return Time.time >= castingTimeEnd ? 0 : castingTimeEnd - Time.time; }
public Transform skillStartPosition = null;
public FireBall fireball = null;
pubilc void Awake() {
col = GetComponent<Collider>();
rb = GetComponent<Rigidbody>();
}
public override void IDLE() {
if (isCasting == true) return;
}
public override void WALK() {
if (isCasting == true) return;
if (Input.GetKey(KeyCode.W))
transform.position += moveSpeed * Time.deltaTime * transform.forward;
if (Input.GetKey(KeyCode.S))
transform.position += moveSpeed * Time.deltaTime * transform.forward * -1;
if (Input.GetKey(KeyCode.A))
transform.position += moveSpeed * Time.deltaTime * transform.right * -1;
if (Input.GetKey(KeyCode.D))
trnasform.position += moveSpeed * Time.deltaTime * transform.right;
}
public override void JUMP() {
if (grounded == false ||
isCasting == true) return;
if (Input.GetKey(KeyCode.Space)) {
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
public override void ATTACK() {
if (grounded == false ||
canAttack == false ||
isCasting == true) return;
if (Input.GetKey(KeyCode.Mouse0)) {
castingTimeEnd = Time.time + castingTime; // skill 시전시, 시전시간 가산
isCasting = true;
}
}
private void Update() {
if (isCasting == true &&
castingTimeRemaining() <= 0f) {
isCasting = false;
canAttack = false;
attackCooldownEnd = Time.time + attackCooldown; // 시전 완료시 재사용 시간 가산
GameObject go = (GameObject)Instantiate(fireball.gameObject);
go.transform.position = skillStartPosition.position;
go.transform.rotation = skillStartPosition.rotation;
go.transform.localScale = Vector3.one;
go.SendMessage("SetFireBallMoveDirection", transform.forward, SendMessageOptions.DontRequireReceiver);
}
if (canAttack == false &&
attackCooldownRemaining() <= 0f) {
canAttack = true;
}
if (grounded) hitDistance = 0.35f;
else hitDistance = 0.15f;
if (Physics.Raycast(transform.position - new Vector3(0, 0.15f, 0),
-transform.up,
hitDistance,
groundLayers))
grounded = true;
else
grounded = false;
}
}
view raw Player.cs hosted with ❤ by GitHub

# other - Time.time 이란?


이러한 구조를 가지고 있다.

즉, Time.time은 게임에서 돌아가고 있는 '게임 내의 System 시계'다 라고 생각하면 편하다.

(현재 시점 N에서 계속 시간이 흐르면, 어느 순간 (N + t)라는 시간에 도달하게 된다. 그 순간 시전이 완료되면서 공격이 나가게 된다.)



# 글을 마치면서...


Command.unitypackage


동작에 대한 정보는 첨부파일에 담겨있다.

해당 명령 패턴에 의해 조작되는 캐릭터를 보고 싶다면, 첨부파일을 받아서 실행시켜보자.



# 참고 자료

 1. GoF의 책 - 'Design Patterns'

 2. 로버트 나이스트롬의 책 - 'Game Programming Patterns'

 3. 위키백과의 '커맨드패턴' - https://goo.gl/UUkBab

'- Unity > 캐릭터 조작' 카테고리의 다른 글

State Pattern (상태 패턴)  (0) 2018.08.31

+ Recent posts