예전부터 의문을 가지고있던 질문입니다

zooloo의 이미지

스타크래프트를 예를들면
일꾼하나를 다른곳에 보내면 일꾼이 그쪽으로 이동합니다
다른유닛을 아무곳에나 보내면 그 역시도 이동시킨곳으로 이동합니다
순간이동하는게 아니라 점진적으로 이동합니다
동시에 다른유닛을 또 다른곳에 보내면 아까보낸 유닛이랑 또 별개로 이동합니다

파이썬의 터틀그래픽같은경우도 즉시 그려지는게 아니라 선이 점점 길어지면서
그려지는데 이런것들이 프로그래밍적으로 어떻게 구현하는건지 정말 궁굼합니다

제가 생각해본건 유닉별로 thread를 각각 만들고 주기적으로 sleep()를 호출하면
가능할거 같긴 하지만 자원을 너무 비효율적으로 사용하는것 같고 코드역시 어마무시하게
복잡해 질것 같습니다

뭐로 뭘 어떻게 하면 저런걸 만들수 있는 건가요?

chocokeki의 이미지

혹시 이런 방법은 아닐까요?
각 유닛의 좌표값이 담겨있는 배열이 있고 그것만 계속 갱신해서 그려주는 방식으로 통합관리?

zooloo의 이미지

배열이든 리스트든 값을 갱신할때 딜레이를 어떻게 줘야할까요? 딜레이없이 그냥 갱신하면 순간이동이랑 똑같아질텐데요..

익명 사용자의 이미지

이동거리 = 속도 * 시간

익명 사용자의 이미지

아시는분 없나요?

...!의 이미지

이미 댓글들에 답이 나왔는데요? 약간 더 부연 설명하자면 버퍼(도화지라고 생각하세요)에 배경과 유닛들을 그리고 화면에 출력합니다. 잠시 기다렸다가 다시 각 유닛들의 위치를 속도에 맞춰서 계산해서 버퍼에 그리고 화면에 출력합니다. 이것을 반복하는 것이지요. 유닛별로 쓰레드를 사용할 이유는 전혀 없습니다. 그냥 셀 애니메이션과 다를 것이 전혀 없지요.

익명 사용자의 이미지

"잠시 기다렸다가 다시" 여기에서
유닛별로 잠시 기다리는걸 어떻게 구현해야 할까요?
제가 궁굼한게 다른게 아니라 어떤식으로 딜레이를 줘야 하느냐입니다
대체 답이 어디에 나와있다는말인가요

ifree의 이미지

유닛이 별개로 행동하는 것이 아니고, 전체 아이템들 중의 일부로 행동하므로 sleep 같은 것을 쓰면 안 되겠죠. 기다리는 것은 그냥 제 자리에 계속 그려주면 되요.
각 유닛에 대해 thread를 쓸 수도 있지만, 어차피 그려지는 시점에서 모든 신을 다시 갱신해서 그려야 하므로 인스턴스로 처리하는 것이 좋을 것입니다.

ymir의 이미지

그냥 간단히 고전적인 방식으로 풀어 보자면..

A 는 (x, y) 에서 (x, y+30) 으로 이동하고, B 는 (x+30, y) 에서 (x, y) 로 이동하고..
속도는 초속 30 pixel 에.. 초당 30 프레임으로 화면을 찍어 낸다고 가정해 보면..
1/30 초 후의 위치는 A(x, y+1), B(x+29, y) 가 되겠죠.
그 다음 1/30 초 후의 위치는 A(x, y+2), B(x+28, y) 가 되겠죠.

한 쪽에서는 매 1/30 초 후에 있을 모든 유닛의 좌표를 계산해서 버퍼에 그리고..
다른 쪽에서 매 1/30 초 마다, 버퍼의 내용을 그대로 화면에 뿌린다면..
자연스럽게 A 와 B 가 원하는 목적지로 이동하는 것처럼 보일겁니다.

매 정해진 시간 간격마다 뭔가를 하는 거라면 타이머 같은 거 쓰면 되겠구요.

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

ifree의 이미지

일반적으로는 FPS가 고정되어 있지 않고 가능한 한 많은 프레임을 그립니다.
저 사양에서는 초 당 30프레임이 안 나올 수도 있습니다.
따라서 이전 프레임과의 시간 차이를 이용하여 계산을 하게 됩니다.

HDNua의 이미지

질문자 분이 그래픽 환경에서 개발을 해보지 않은 사람임을 가정하고 설명하는 점 양해바랍니다.
아마 다른 분들도 이걸 다 설명하는 게 지나치게 길어서 그냥 넘기신 것 같아요.

보통 프로그래밍 언어 입문서를 뗄 때쯤엔 언어의 문법은 어느 정도 알지만,
실제로 그래픽 환경에서 이것이 어떻게 적용되는지에 대한 개념은 없는 경우가 많습니다.

간단하게 설명하면, 무한히 루프를 도는 겁니다. 기본은 다음 방식입니다.

// 프로그램 진입점입니다.
int main(void) {
	// 프로그램은 강제로 종료하지 않는 이상 끝나지 않습니다.
	while (true) {
		// 내용을 업데이트합니다.
		update();
		// 화면에 그림을 그립니다.
		draw();
	}
}
// 내용을 업데이트합니다.
void update() {
	// 사용자 키 입력이 감지되었다면
	if (key_pressed()) {
		// 적당히 내용을 업데이트합니다.
	}
}
// 화면에 그림을 그립니다.
void draw() {
	// 잘 화면에 그립니다.
}

이 꼴이 게임에 적용되면 이런 모양이 됩니다.
1. 유니티 개발자라 C# 기준으로 작성했어요.
2. 별도로 정의되지 않은 메서드는 알아서 잘 정의되어있다고 가정하세요.

class Program {
	// 프로그램 진입점입니다.
	public static void Main() {
		// 게임 관리자 개체를 생성합니다.
		GameManager gm = new GameManager();
 
		// 프로그램은 강제로 종료하지 않는 이상 끝나지 않습니다.
		while (true) {
			// 내용을 업데이트합니다.
			gm.Update();
			// 화면에 그림을 그립니다.
			gm.Draw();
		}
	}
}
 
// 게임 관리자입니다.
class GameManager {
	List<GameObject> _gameObjects;
 
	// 내용을 업데이트합니다.
	public void Update() {
		// 모든 업데이트 이전의 작업을 처리합니다.
		Preupdate();
		// 등록된 유닛에 대해 필드 업데이트를 진행합니다.
		foreach (GameObject go in _gameObjects) {
			go._Update();
		}
		// 모든 업데이트 이후의 작업을 처리합니다.
		Postupdate();
	}
	// 화면에 그림을 그립니다.
	public void Draw() {
		// 모든 그리기 이전의 작업을 처리합니다.
		Predraw();
		// 등록된 게임 개체들을 모두 새로 그립니다.
		for (GameObject go in _gameObjects) {
			go._Draw();
		}
		// 모든 그리기 이후의 작업을 처리합니다.
		Postdraw();
	}
}
// 게임 개체입니다.
class GameObject {
	public Vector2 _position; // 현재 게임 개체의 위치 필드입니다.
	public Vector2 _velocity; // 현재 게임 개체의 속도 필드입니다.
	public Vector2 _acceleration; // 현재 게임 개체의 가속도 필드입니다.
 
	// 내용을 업데이트합니다. (주의: 이 메서드는 GameManager만 호출합니다.)
	void _Update() {
		Vector2 newPosition;
 
		// 시간 관리자로부터 시간 변량을 획득합니다.
		// 시간 변량은 (현재 프레임 시간 - 이전 프레임 시간)입니다.
		float dt = TimeManager.GetDeltaTime();
 
		// 새 위치를 위치-속도-가속도 공식을 이용하여 계산합니다.
		_position.x = _position.x + _velocity.x * dt + 0.5 * _acceleration.x * dt * dt;
		_position.y = _position.y + _velocity.y * dt + 0.5 * _acceleration.y * dt * dt;
 
		// 새 위치로 개체를 옮깁니다.
		_position = newPosition;
 
		// 내용을 업데이트합니다.
		Update();
	}
	// 화면에 개체를 그립니다. (주의: 이 메서드는 GameManager만 호출합니다.)
	void _Draw() {
		// 업데이트된 위치를 중심으로 그림을 그립니다.
		DrawFromPosition(_position);
	}
 
	// 내용을 업데이트합니다.
	public virtual void Update() { }
	// 화면에 개체를 그립니다.
	public virtual void Draw() { }
}

만약 특정 개체의 속도를 바꾸는 ChangeVelocity() 메서드를 작성하고 싶다면, 이렇게 하면 됩니다.

// 개체의 속도를 바꿉니다.
// vx: X축 속력입니다.
// vy: Y축 속력입니다.
void ChangeVelocity(float vx, float vy) {
	_velocity = new Vector2(vx, vy);
}

키를 누를 때마다 X, Y축 속도가 1씩 증가하는 개체 StrangeUnit이 있다면 이렇게 하겠죠.

class StrangeUnit: GameObject {
	// 내용을 업데이트합니다.
	public override void Update() {
		// 사용자 키 입력이 감지되었다면
		if (KeyPressed()) {
			ChangeVelocity(_velocity.x + 1, _velocity.y + 1);
		}
	}
}

이것이 어떻게 돌아가는지를 설명하면 이렇습니다.
1. GameManager가 루프를 돌고 있습니다.
2. Update의 루프가 수행되면서, GameManager에 등록된 GameObject의 Update()가 모두 호출됩니다.
3. Draw의 루프가 수행되면서, GameManager에 등록된 GameObject의 Draw()가 모두 호출됩니다.
4. 1로 복귀

따라서 우리는 GameObject의 Update 메서드만 오버라이드해주면,
StrangeUnit의 위치 이동은 자동으로 이루어지는 것입니다.
당연한 거라고 생각하실 수가 있는데 전혀 그렇지 않아요. 이건 아주 정교한 설계 패턴입니다.

질문에 나온, "잠깐 기다렸다가 다시"는 결국 이렇게 구현됩니다.

class TerrenSCV: GameObject {
	// 개체가 생존한 시간입니다.
	float _time = 0;
 
	// 내용을 업데이트합니다.
	public override void Update() {
		// 개체의 물리 상태를 업데이트합니다.
		UpdatePhysicalState();
		// 사용자 입력에 대한 행동을 정의합니다.
		UpdateStateWithInput();
		// 시간을 증가시킵니다.
		_time += TimeManager.GetDeltaTime();
	}
 
	// 개체의 물리 상태를 업데이트합니다.
	void UpdatePhysicalState() {
 
		// 움직이는 중이라면
		if (_isMoving) {
 
			// _time이 1이 될 때까지 대기합니다.
			// _time은 Update() 메서드에서 업데이트됩니다.
			if (_time < 1f) { 
 
			}
			// _time이 1 이상이라면 이제부터 속도와 가속도를 줍니다.
			// 더 정교하게 만드는 건 알아서 해보세요.
			else {
				_velocity = new Vector2(some speed x, some speed y);
				_acceleration = new Vector2(some acceleration x, some acceleration y);
			}
		}
		else {
			// 속도와 가속도를 0으로 만듭니다.
			_velocity = new Vector2(0, 0);
			_acceleration = new Vector2(0, 0);
		}
	}
	// 사용자 입력에 대한 행동을 정의합니다.
	void UpdateStateWithInput() {
		// 맵에서 오른쪽 마우스 클릭이 발생했다면
		if (RightClickedOnMap()) {
			_isMoving = true;
		}
	}
}

한 번에 이해하긴 힘들 거라고 생각하고, Unity 등의 게임 개발 도구를 직접 익히는 것을 추천합니다.

저는 이렇게 생각했습니다.

HDNua의 이미지

중요한 부분을 말하지 않아서 정리합니다.

유닛이 병렬적으로 움직여야 한다고 해서 각 유닛 별로 thread를 생성하지 않습니다. 그냥 다음을 반복하면 됩니다.

update all position in unit list
redraw all units in unit list

저는 이렇게 생각했습니다.

zooloo의 이미지

고맙습니다.
덕분에 어떤방식으로 만드는지 이해가 되었습니다.

koreaphb의 이미지

파이썬으로 그래픽쪽을 다뤄본적이 없기 때문에, 거북이라 말씀하신걸 찾아 보니

파이썬 거북이 그래픽 (Python Turtle Graphics) 모듈을 말씀하시는거라면, 기본적으로

질문자께서 GDI/GDI+ 나 BITMAP을 화면에 뿌리는 방식같은 것을 모르시는거라 판단이 되네요.

해당 모듈은 그저 API에 따라서 이동해서 자체적 Thread 처리로 이미지를 그리는 것 같군요.

쉽게 말해서 그래픽 API들이 A를 (x,y) 지점 까지 1000ms 로 이동해가 아니라

A를 x,y 지점으로 1 pixel/s 로 이동을 원하는 경우

1. 화면을 뿌릴 메모리에 이미지를 지운다.

2. 메모리에 배경을 그린다.

3. 현재 시점의 A의 좌표를 계산한다.(큰 프로그램일 경우 이부분은 다른 부분에서 처리하겠죠. 그럴땐 그저 현재 있어야될 위치만 읽어오면 되겠죠)

4. 3에서 계사된 A의 좌표에 A의 오브젝트를 그린다.

5. 나머지 모든 오브젝트를 그린다.

6. 다 그린 이미지를 화면에 뿌린다.

이런 순서로 진행됩니다. 또한 모든 이미지를 메모리 한곳에다가 그린다음 화면을 갱신시키는것을

더블버퍼링이라 합니다.

질문자께서 화면을 그리는 방법을 더 잘알고 싶다면 아마도 파이썬은 그것에 약한 언어일 수도 있다고 생각이 됩니다.

C# 까진 C++ GDI/GDI+를 랩핑하여 사용하는듯 해서 성능이 크게 나쁘지 않지만, 파이썬으로는 GUI도 만들어 본적이 없기에, 해당 언어에 대해서 자세한 설명을 해드리긴 어렵겠군요.

아무튼 질문이 해결되셨다면, 다행이시고 만약에 이해가 안되는 부분이 있다면 리플을 달아 주시거나 직접 다른 언어들이 어떻게 화면을 그리는지 한번 찾아보시면 될 것 같군요.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.