Python script

분류:프로그래밍


Python을 게임 Script로 활용하자.

2001/07/23 07:23붉은미르


http://www.g-matrix.pe.kr 의 game school에 올린 예제를 중심으로 강의가 구성되어질 것입니다.
다른 님들께서 생각들이나 강의들을 올려주시면 더욱더 감사하겠습니다.


강의자에게 한 마디하기 & 강의 소감 & 고칠 점


질문과 답

질문은 가능하면 각 강좌(?)의 끝부분에 해주시고 여기에는 거기에 넣기에 애매하다 싶은 것을 적어주시 바랍니다.


목차

  1. 파이썬이란?
  2. 스크립터로 사용할 수 있는 공개용 언어들은 어떤 것들이 있는가?
  3. 수행능력
  4. 파이썬에서 Extending and Embedding
  5. 들어가기 앞서...
  6. 예제를 실행하기
  7. C/C++에서 파이썬 함수를 호출하기(예제 중심)
  8. C/C++에서 파이썬 클래스를 호출하기(예제 중심)
  9. OpenGL/GLUT 함수를 파이썬에서 호출하자(예제 중심)
  10. Swing을 이용해서 C++를 파이썬으로 확장하자. => 언제할지 모르겠습니다. (딴 사람이 해도 됩니다.)
  11. DirectX 를 파이썬에서 호출하자(예제 중심) => 언제할지 모르겠습니다. (딴 사람이 해도 됩니다.)
  12. Game Script로써 파이썬을 어떻게 활용할 것인가? => 언제할지 모르겠습니다. (딴 사람이 해도 됩니다.)
  13. 참고자료

파이썬이란?

초고수준, 접착성 언어입니다. 쉽고 단순해서 누구나 쉽게(?) 배울 수 있습니다. 국내에서 작년(2000년)부터 소개되어 인기를 얻고 있는 언어입니다.

저의 어설픈 설명보다는 '파이썬 공식 홈페이지 <http://www.python.org>'을 참조하시면 더욱더 좋을 것입니다. 영어가 부담스러우신 분들은 '한국 파이썬 공식 홈페이지 <http://www.python.or.kr>'을 방문하시면 많은 정보를 얻을 수 있을 것입니다.


스크립터로 사용할 수 있는 공개용 언어들은 어떤 것들이 있는가?

Lua <http://www.lua.org>

CSS <http://http://ibk-software.ch/css/index.html>

Small <http://www.compuphase.com/small.htm>

S-Lang <http://www.s-lang.org/>

Simkin for C++ <http://www.simkin.co.uk/>

Python <http://www.python.org>

ETC....

여기에서 있는 것들은 전부 다 GPG포럼에서 언급되었던 것입니다. 소개해주신분들에게 감사의 말씀을 전해드립니다.

제가 여기에서 가장 주목을 하고 있는 언어는 두개입니다. Lua와 Python입니다. 각각의 쓰임새에 대해서 짧게 설명을 하면 다음과 같이 할 수 있을 것 같습니다.

  *  비주업 노벨 스타일의 게임 또는 문명 같은 턴 베이스 게임을 만들때에 좋음.
  * 게임의 대부분을 파이썬으로 구성하고 속도가 필요한 그래픽부분만 C 또는 C++로 구성함.
  * 예가 될지는 모르겠지만.
     * 메가폴리(http://www.megafolly.co.kr/)의 LOVE(http://www.my-love.co.kr/)

  * 임베이디딩을 목적으로 하는 경우에는 가볍고 빨라서 좋음.
  * 대부분을 C 또는 C++로 짜고 일부 자주 수정해야 되는 부분만 Lua로 작성함.
  * 예가 될지는 모르겠지만.
     * MMORPG의 서버에...사용.


수행능력

게임에서 속도라는 것은 상당히 중요한 요소입니다. 아무래 컴이 빨라져서 속도에 신경을 쓰지 않아도 된다고 하지만, 여전히 빠른 속도는 중요합니다. 그럼, 파이썬의 속행 능력은 어느 정도가 될까요? 느려서 사용하기 힘들지 않을까? 쓰고도 남을만큼 충분할까?

이 말로 답을 대신합니다. Blade Of Darkness <http://www.rebelact.net/darkness/> 와 Kingdon Under Fire <http://www.kuf.co.kr/>에 Python이 사용되었다.


파이썬에서 Extending and Embedding

Extending는 C/C++등을 파이썬으로 확장하는 것입니다.

Embedding은 C/C++에서 파이썬을 호출(또는 사용)하는 것입니다.

파이썬을 사용하는 경우에는 Embedding보다는 Extending을 더 많이 사용한다. 즉, 파이썬으로 프로그램을 짠 후 수행능력을 느르면 느린 부분만을 다시 C/C++로 작성한 뒤에 Extending을 해서 파이썬에서 사용하는 방법을 택하는 것이 일반적입니다. 다시금 정리하면,

그럼 우리는 Extending과 Embedding이 다 필요하다. 그러나, 접근은 Embedding부터하도록 하겠습니다.


들어가기 앞서...

이 글을 읽는 사람은 아래의 경우를 만족한다고 가정합니다.

사실은 이것을 꼭 만족할 필요는 없다.

Python을 대해서 알고 싶은 사람은 맨밑의 '참고자료'를 보기 바랍니다.

그러나, 필히 '참고 자료 -> C 확장 모듈 만들기'은 꼭 읽어보기 바란다.


예제를 실행하기

예제 중심의 강좌이기 때문에 예제 실행될 필요가 있다.


C/C++에서 파이썬 함수를 호출하기(예제 중심)

우선 예제를 보여주고 각 부분에 대해서 설명을 하는 형식을 취하도록 하겠습니다. Python Embedding에 관한 내용이라고 생각하시면 됩니다.

glEmbed.cpp

// 이 프로그램은 Python을 임베이딩을 하는 것에 대한 간단하 예제이다.
// python이 C형태로 되어있다.
// 간단한 제안이다. 추가적인 확장은 좀더 노력해야될 것 같다.
// 2001/7/18/수

#include <windows.h>
#include <gl/glut.h>
#include 'Python.h'

PyObject  *module, *func, *rc;
float   g_Ry = 0;

void RenderScene(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
        glColor3f( 1.0f, 1.0f, 1.0f );
        glTranslatef( 0.0f, 0.0f, -5.0f );
        glRotatef( g_Ry, 0, 1, 0);
        glutWireTeapot( 1.0f );

    glFlush();
    glutSwapBuffers();
}

void Idle()
{
    RenderScene();

    rc = PyObject_CallObject(func,  Py_BuildValue('(f)', g_Ry) );
  if (rc == NULL) { PyErr_Print(); }
    else {
        PyArg_Parse( rc, 'f', &amp;g_Ry );
        Py_DECREF(rc);
    }
}

void ChangeSize(GLsizei w, GLsizei h)
{
    glViewport( 0, 0, w, h);
    float fAspect = (GLfloat)w/(GLfloat)h;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
        gluPerspective( 90, fAspect, 1.0f, 5000.0f );
}

void SetupRC(void)
{
    glClearColor( 0.0f, 0.0f, 1.0f, 1.0f );
}

void main(int argc, char **argv)
{
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
    glutInitWindowSize( 640, 480);
    glutCreateWindow('Embedding Python');
    glutDisplayFunc(RenderScene);
    glutIdleFunc(Idle);
    glutReshapeFunc(ChangeSize);

    SetupRC();

    Py_Initialize();
    PySys_SetArgv( argc, argv);
    PyRun_SimpleString('print 'Embedding Python' ');

    module = PyImport_ImportModule('teapot');
         if (module == NULL) { PyErr_Clear();   printf('Unable to import embed module');      }

    func = PyObject_GetAttrString(module, 'move');
         if (func == NULL) { PyErr_Clear(); printf('Unable to bind to hello function in detect module ');   }

    glutMainLoop();

    Py_Exit(0);
}

teapot.py

# 임베이딩을 테스트하기 위해서 만들어졌다.
# c형태의 python을 어떻게 임베이딩하는지는 간단하게 배을수 있을 것이다.

def move(a):
    a = a + 1
    if a > 360: a = 0
    return a

우선 glEmbed.cpp을 살펴보기로 하자.

'); => 파이썬 명령어인 'print'를 문자열 단위 실행 함수로 실행시켰다. console창에 'Embedding Python'이라고 출력된다.

간단하게나만 소스에 대한 개력적인 소개가 끝이 났다.

teapot.py에서 'def move(a):'과 'return a'을 제외한 부분을 얼마던지 바꾸어도 된다.

즉, a = a + 1을 a = a + 4 이런씩으로 바꾸어 그 결과를 지켜보기 바란다.

참고자료에 있는 'C확장 모듈 만들기'를 꼭 읽어보기를 바란다.


C/C++에서 파이썬 클래스를 호출하기(예제 중심)

Python파일을 중점적으로 살펴보기 바란다. 아래의 예제를 실행하기 위해서는 random.pyc가 추가적으로 필요하다.

#include <windows.h>
#include <glglut.h>
#include 'Python.h'

PyObject  *module, *request, *func, *rc;
float   g_Ry = 0;

void RenderScene(void)
{
    glClear(GL_COLOR_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
        glColor3f( 1.0f, 1.0f, 1.0f );
        glTranslatef( 0.0f, 0.0f, -5.0f );
        glRotatef( g_Ry, 0, 1, 0);
        glutWireTeapot( 1.0f );

    glFlush();
    glutSwapBuffers();
}

void Idle()
{
    RenderScene();

    // class에서 method를 호출한다.
    rc = PyObject_CallMethod(request,  'wave', 'f', g_Ry);
         if (rc == NULL) {  PyErr_Print(); }
    else {
        PyArg_Parse( rc, 'f', &amp;g_Ry );
        Py_DECREF(rc);
    }
}

void ChangeSize(GLsizei w, GLsizei h)
{
    glViewport( 0, 0, w, h);
    float fAspect = (GLfloat)w/(GLfloat)h;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
        gluPerspective( 90, fAspect, 1.0f, 5000.0f );
}

void SetupRC(void)
{
    glClearColor( 0.0f, 0.0f, 1.0f, 1.0f );
}

void main(void)
{
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
    glutInitWindowSize( 640, 480);
    glutCreateWindow('Embedding Python');
    glutDisplayFunc(RenderScene);
    glutIdleFunc(Idle);
    glutReshapeFunc(ChangeSize);

    SetupRC();

    Py_Initialize();
    PyRun_SimpleString('print 'Embedding Python' ');

    // teapot_plus.py
    module = PyImport_ImportModule('teapot_plus');
         if (module == NULL) { PyErr_Clear();   printf('Unable to import embed module');      }

    // class name
    request = PyObject_CallMethod(module, 'teapot_plus', NULL);
         if (request == NULL) { PyErr_Clear(); printf('Unable to bind to teapot_plus function in detect module');   }

    glutMainLoop();

    Py_Exit(0);
}

# 임베이딩을 테스트하기 위해서 만들어졌다.
# c형태의 python을 어떻게 임베이딩하는지는 간단하게 배을수 있을 것이다.

from random import Random
from time   import *

class teapot_plus:
    def move(self):     # move(self, a)와 move(self)의 위치가 바뀌면 Error가 발생한다. 헐헐헐
        a = 30
        return a

    def move(self, a):
        a = a + 1
        if a > 360:
            a = 0
            print a
        return a

    def wave(self, a):
        g  = Random(time()) # seed(씨앗)
        return g.uniform(-10, 10) + a

앞의 예제와 이번 예제에서 C/C++를 실펴보면 그렇게 크게 변한 것은 못 느낄것이다. 그러나 파이썬을 많은 변화가 있었다.


OpenGL/GLUT 함수를 파이썬에 호출하자(예제 중심)

C/C++ 코드는 전혀 손을 대지 않고 Python부분만을 수정함으로써 게임을 진행할 수 있다면 어떨까요? OpenGL에 사용되는 함수들을 Python에서 C/C++에서 사용하듯이 똑같이 사용한다면 스크립터인 Python만으로 게임을 만들수도 있지 않을까요?

OpenGL, 좀더 정확하게 말하면 GLUT를 Python에서 사용할 수 있도록 Extending(확장)시키면 C/C++에서는 Python을 딱 한번만 호출하고 나머지는 Python에서 다 처리하도록 할 수 있습니다. 재미있을 것 같지 않습니까?

참고로 이야기하면 앞서 두 예제는 Embedding에 관한 예제이다.

역시나 여기서 예제를 나열한다.

#include <windows.h>
#include <glglut.h>
#include 'Python.h'

PyObject  *module, *func, *rc;
float   g_Rx = 0, g_Ry = 0, g_Rz = 0;

void InitFakeGL(void);

void Python_Render()
{
    rc = PyObject_CallObject(func,  NULL );
         if (rc == NULL) {  PyErr_Print(); }
    else {
            //PyArg_Parse( rc, 'f', &amp;g_Ry );
        Py_DECREF(rc);
    }
}

void RenderScene(void)
{
         Python_Render();
}

void Idle()
{
    Python_Render();
}

void ChangeSize(GLsizei w, GLsizei h)
{
    glViewport( 0, 0, w, h);
    float fAspect = (GLfloat)w/(GLfloat)h;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
        gluPerspective( 90, fAspect, 1.0f, 5000.0f );

}

void SetupRC(void)
{
    glClearColor( 0.0f, 0.0f, 1.0f, 1.0f );
}

void main(int argc, char **argv)
{
    glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB);
    glutInitWindowSize( 640, 480);
    glutCreateWindow('Embedding Python');
    glutDisplayFunc(RenderScene);
         glutReshapeFunc(ChangeSize);

    SetupRC();

    Py_Initialize();
    PySys_SetArgv( argc, argv);

    /* Add a static module */
    InitFakeGL();

    PyRun_SimpleString('print 'Embedding Python' ');

    module = PyImport_ImportModule('fakegl');
         if (module == NULL) {  PyErr_Clear();  printf('Unable to import embed module
');   }
    PyRun_SimpleString('print 'PyImport_ImportModule' ');

    func = PyObject_GetAttrString(module, 'render');
         if (func == NULL) {    PyErr_Clear();  printf('Unable to bind to hello function in detect module
'); }
    PyRun_SimpleString('print 'PyObject_GetAttrString' ');

    glutMainLoop();

    Py_Exit(0);
}

static PyObject *my_call_glRotatef(PyObject *self, PyObject *args)
{
    float angle, x, y, z;

    if (!PyArg_ParseTuple(args, 'ffff', &amp;angle, &amp;x, &amp;y, &amp;z))        return NULL;
    glRotatef( angle, x, y, z);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glTranslatef(PyObject *self, PyObject *args)
{
    float x, y, z;

    if (!PyArg_ParseTuple(args, 'fff', &amp;x, &amp;y, &amp;z))     return NULL;
    glTranslatef( x, y, z);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glColor3f(PyObject *self, PyObject *args)
{
    float r, g, b;

    if (!PyArg_ParseTuple(args, 'fff', &amp;r, &amp;g, &amp;b))     return NULL;
    glColor3f( r, g, b);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glVertex3f(PyObject *self, PyObject *args)
{
    float x, y, z;

    if (!PyArg_ParseTuple(args, 'fff', &amp;x, &amp;y, &amp;z))     return NULL;
    glVertex3f( x, y, z);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glClear(PyObject *self, PyObject *args)
{
    int  mask;

    if (!PyArg_ParseTuple(args, 'i', &amp;mask))        return NULL;
    glClear( mask );

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glMatrixMode(PyObject *self, PyObject *args)
{
    int  mode;

    if (!PyArg_ParseTuple(args, 'i', &amp;mode))        return NULL;
    glMatrixMode( mode );

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glLoadIdentity(PyObject *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ''))        return NULL;
    glLoadIdentity();

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glBegin(PyObject *self, PyObject *args)
{
    int n;

    if (!PyArg_ParseTuple(args, 'i', &amp;n))       return NULL;
    glBegin( n );

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glEnd(PyObject *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ''))        return NULL;
    glEnd();

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glFlush(PyObject *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ''))        return NULL;
    glFlush();

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glutWireTeapot(PyObject *self, PyObject *args)
{
    float f;

    self = self;
    if (!PyArg_ParseTuple(args, 'f', &amp;f))       return NULL;

    glutWireTeapot(f);

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *my_call_glutSwapBuffers(PyObject *self, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ''))        return NULL;
    glutSwapBuffers();

    Py_INCREF(Py_None);
    return Py_None; }

static PyMethodDef my_c_fakeGL_methods[] = {
    {'glTranslatef',        my_call_glTranslatef,       METH_VARARGS},

    {'glRotatef',       my_call_glRotatef,          METH_VARARGS},

    {'glColor3f',       my_call_glColor3f,          METH_VARARGS},

    {'glVertex3f',      my_call_glVertex3f,     METH_VARARGS},

    {'glClear',     my_call_glClear,            METH_VARARGS},

    {'glMatrixMode',        my_call_glMatrixMode,       METH_VARARGS},

    {'glLoadIdentity',  my_call_glLoadIdentity,     METH_VARARGS},

    {'glBegin',     my_call_glBegin,            METH_VARARGS},

    {'glEnd',           my_call_glEnd,          METH_VARARGS},

    {'glFlush',     my_call_glFlush,            METH_VARARGS},

    {'glutWireTeapot',           my_call_glutWireTeapot,        METH_VARARGS},

    {'glutSwapBuffers', my_call_glutSwapBuffers,             METH_VARARGS},

    {NULL,      NULL}
};

void InitFakeGL(void)
{
    PyImport_AddModule('my_c_fakeGL');
    Py_InitModule('my_c_fakeGL', my_c_fakeGL_methods);
}

fakegl.py

# --------------------------------------------------------

GL_COLOR_BUFFER_BIT =  0x00004000 #16384    # 0x00004000

# MatrixMode
GL_MODELVIEW  = 0x1700
GL_PROJECTION = 0x1701
GL_TEXTURE    = 0x1702

# BeginMode
GL_POINTS         = 0x0000
GL_LINES          = 0x0001
GL_LINE_LOOP      = 0x0002
GL_LINE_STRIP     = 0x0003
GL_TRIANGLES      = 0x0004
GL_TRIANGLE_STRIP = 0x0005
GL_TRIANGLE_FAN   = 0x0006
GL_QUADS          = 0x0007
GL_QUAD_STRIP     = 0x0008
GL_POLYGON        = 0x0009

# --------------------------------------------------------
# 주의: OpenGL은 C함수이다. 가능하면 Python에서 C함수형태로 접근할 것
#       Python은 들어쓰기가 중요한데...역시나 gl 과 glut에서 똑같이 중요하다.

from my_c_fakeGL import *

def glRender(Ry):
    glClear(GL_COLOR_BUFFER_BIT)

    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

    glColor3f( 1, 0, 0 )
    glTranslatef( 0, 0, -5 )
    glRotatef(Ry, 0, 1, 0)
    glutWireTeapot( 1 )

    glColor3f( 1, 1, 1 )
    glTranslatef( -15, 0, -5 )
    glutWireTeapot( 1 )

    glFlush()
    glutSwapBuffers()

def render():
    Ry = 0
    count = 0
    while Ry < 360:
        glRender(Ry)
        if Ry > 360 :
            Ry = 0
            print count
            count = count + 1
        Ry = Ry + 1

이 전의 예제들은 Python으로 만들어진 것을 C/C++에서 읽어들여서 실행시키는 것이었습니다. 이것은 'PyImport_ImportModule()' 함수에 의해서 이루어집니다. 이것을 'Python Embedding'이라고 합니다. 이번 예제는 그와 반대되는 부분도 등장합니다. 즉, C/C++에서 만든 함수를 Python에서 사용하고자합니다. 이것을 'Python Extending'이라고 합니다.

소개한 예제에서 가장 눈여보아야할 부분은 다음 부분입니다.

void InitFakeGL(void)
{
    PyImport_AddModule('my_c_fakeGL');
    Py_InitModule('my_c_fakeGL', my_c_fakeGL_methods);
}

static PyMethodDef my_c_fakeGL_methods[] = {
    {'glTranslatef',        my_call_glTranslatef,       METH_VARARGS}
    {NULL,      NULL}
}

static PyObject *my_call_glTranslatef(PyObject *self, PyObject *args)
{
    float x, y, z;

    if (!PyArg_ParseTuple(args, 'fff', &amp;x, &amp;y, &amp;z))     return NULL;
    glTranslatef( x, y, z);

    Py_INCREF(Py_None);
    return Py_None;
}

C/C++ 부분을 살피면

Python 부분을 살피면

C/C++에는 'render()'만을 호출했습니다. 하지만, python에서는 다른 함수인 'glRender()'를 만들어서 호출하고 있습니다. 또한 C/C++에서 python 모듈로 등록한 'my_c_fakeGL'을 불러다가 사용하고 있습니다. C/C++는 수정하지 않고, 단지 Python을 수정하는 것만으로도 프로그램에 변화를 가져옵니다.

___

참고

http://www.mcmillan-inc.com/embed.html


참고 자료


분류:스크립팅