スペースハリアー風味の床描画(3)

スペースハリアー風味の床描画プログラムを手直ししてみました。

あんまり見た目は変わってないですが、影描画の処理の追加を行っています。

game_ss03 ありがちなポスト処理とか入れてみたり。

描画オブジェクトを Z ソートしていないため、前後関係があやしいですけど…

単純な半透明影を落としてしまうと同じ場所に落ちた影同士が段々と暗くなっていくため、影だけを描画するレンダーターゲットを用意して、最後に合成しています。(影の解像度が低く見えるのは、レンダーターゲットの解像度が低いからです。)

システム自体は Nebula Device 2 を使用している為、影の描画部分以外はシステム内蔵のシェーダーをそのまま使用しています。

 

スペースハリアー風味の床描画(2)

前記事のデータを使用して、実際の描画を行ってみました。

(描画には NebulaDevice2 を使用)

nngame02 こんな感じ。

nngame04 試しに天井を描画。

VGAでの描画を行っているため、テクスチャには 1024×512 のサイズを使用しています。

スペースハリアー(多分アウトランも)に搭載されているハードウェアは、同時に2面の処理が行えるようになっていたらしいのでですので、同じ様な実装をしてみました。縦方向にも伸ばせるようになっています。

特殊な実装は何もせずに、単純に1ラインを描画するテクスチャ付きポリゴンを力技で描いているだけです。

ちなみにジャギが見苦しいので少しフォグをかけてあります。

スペースハリアー風味の床描画(1)

ポリゴンによる 3D 空間の描画が出来なかった時代に、画面の奥に向かって進行するゲームを開発するには、いくつかの手法がありました。

そのひとつに水平同期割り込み時に VRAM の読み取り位置を書き換えることによる変形処理で擬似的な 3D 表示を行っているものがあり、自分がすぐに思い浮かぶのはスペースハリアーだったりします。

というわけで、スペースハリアーの床(ラスター処理に渡すための画像)を描画するプログラムを作成してみました。

# -*- coding: utf-8 -*-
##
# ----------------------------------------------------------------- import(s)
import Image, ImageDraw


# ------------------------------------------------------------------ param(s)
IMAGE_W = 512
IMAGE_H = IMAGE_W >> 1
IMAGE_HALF_W = IMAGE_W >> 1
IMAGE_LINE_W = IMAGE_W >> 4
RENDER_COUNT = IMAGE_W
IMAGE_H_ADJUST = -4

COLOR_0 = "#000000"
COLOR_1 = "#FFFFFF"


# --------------------------------------------------------------- function(s)
# ===========================================================================
##
#   @brief  x
#
#   @param [in] oCDraw  x
#
def render_image(oCDraw):

    for c in range(RENDER_COUNT):

        if(c & 1):
            strColor_0 = COLOR_0
            strColor_1 = COLOR_1
        else:
            strColor_0 = COLOR_1
            strColor_1 = COLOR_0

        for n in range(IMAGE_LINE_W):
            nX = (IMAGE_LINE_W * c ) + n

            oCDraw.line(
                (
                    (IMAGE_HALF_W, IMAGE_H_ADJUST),
                    (IMAGE_HALF_W + nX, IMAGE_H)
                ),
                fill=strColor_0
            )

            oCDraw.line(
                (
                    (IMAGE_HALF_W, IMAGE_H_ADJUST),
                    (IMAGE_HALF_W - nX, IMAGE_H)
                ),
                fill=strColor_1
            )


# ===========================================================================
##
#
def main():

    oCImage = Image.new("RGB", (IMAGE_W, IMAGE_H), "white")
    oCDraw = ImageDraw.Draw(oCImage)

    render_image(oCDraw)

    strFilename = "export_w%04d_h%04d.png" % (IMAGE_W, IMAGE_H)
    oCImage.save(strFilename, "PNG")



if(__name__ == "__main__"):
    main()



# --------------------------------------------------------------------- [EOF]

PIL で線を引いているだけのプログラムなんですけどね。

実行すると以下のような画像が生成されます。

export_w0256_h0128

スペースハリアーは、 512×256 ドットのデータを内部に保持しているようですので、 IMAGE_W には 512 を指定すると、もっともそれっぽい[1. と、自分では思っている]データが作成されます。

このデータを使用して擬似 3D な空間を表現するには、ラインごとにポリゴンを分割しなければなりませんが、細長い画像にしてしまえば javascript なんかでも描画出来るかも。

[wpdm_file id=37]

PEP8 によるコーディングスタイルの統一

Python プログラミングをする過程で、かなり適当だったコーディングスタイルを統一させようと思って、 PEP8 に準拠させるためのチェックツールを導入してみることにしました。

手持ちの Python コードは、趣味で作っている物から仕事で作成しているものまでかなりの分量があるのですけど…自分の作成したコードはことごとく、規約に準拠していないというのが判りました。

 

ArtRage4 用の Custom Sizes を生成

ArtRage は、初期状態だと新規作成時に A4 や B5 といったサイズが用意されていません。
もっとも、カスタムサイズとして追加することが可能なので、それ自体はたいした問題ではないのですが、ひとつひとつ追加するのがちょっと手間でしたので、試しに自動生成するスクリプトを書いてみました。(動作は、 ArtRage4(4.0.4) で確認しました。)

使い方はこんな感じです。

$ python artrage_papersize.py A4 A5 B5 --dpi 300

上記の例では、

  • A4 Land.spr … A4横 300dpi
  • A4 Port.spr … A4縦 300dpi
  • A5 Land.spr … A5横 300dpi
  • A5 Port.spr … A5縦 300dpi
  • B5 Land.spr … B5横 300dpi
  • B5 Port.spr … B5縦 300dpi

というファイルがカレントフォルダに生成されます。

生成されたファイルは、必要に応じて ArtRage の Custom Sizes に保存してください。

#!/usr/bin/env  python
# -*- coding: utf-8 -*-
#
# ------------------------------------------------------------------ import(s)
import  sys
import  struct
import  argparse

# ------------------------------------------------------------------- param(s)
ARTRAGE_HEAD  = u"ARSizePresetFileVersion-1\r\n"
PAPER_SIZE    = {
  "A" : { "X" :  841, "Y" : 1189 },
  "B" : { "X" : 1030, "Y" : 1456 },
}
MM_INCH       = 0.03937

# ---------------------------------------------------------------- function(s)

# ============================================================================
##
#
def calc_size( fX, fY ):

  return( fY / 2, fX )

# ============================================================================
##
#
def export_file( strFilename, fX, fY, nDPI ):

  with open( strFilename, "wb" ) as hFile:
    hFile.write( ARTRAGE_HEAD.encode( "utf-16-le" ) )
    hFile.write( struct.pack( "BBBBIIf", 0x01, 0x34, 0x00, 0xFF, 4, 0,   fX ) )
    hFile.write( struct.pack( "BBBBIIf", 0x02, 0x34, 0x00, 0xFF, 4, 0,   fY ) )
    hFile.write( struct.pack( "BBBBIIf", 0x01, 0x31, 0x00, 0xFF, 4, 0, nDPI ) )
    hFile.write( struct.pack( "BBBBIII", 0x00, 0x34, 0x00, 0xFF, 4, 0,    0 ) )
    hFile.close()

# ============================================================================
##
#
def main():

  oCParser  = argparse.ArgumentParser( description = "ArtRage Paper Generator" )
  oCParser.add_argument(
    "paper",
    help  = "Set paper (ex: A4, B5)",
    nargs = "+"
  )
  oCParser.add_argument(
    "-d", "--dpi",
    help    = "Set dpi",
    default = 300
  )

  oCParams  = oCParser.parse_args( sys.argv[ 1: ] )

  for strPaper in oCParams.paper:

    if( len( strPaper ) != 2 ):
      continue
    if( strPaper[ 0 ].upper() not in ( "A", "B" ) ):
      continue

    try:
      strP  = strPaper[ 0 ].upper()
      nSize = int( strPaper[ 1 ] )
      nDPI  = oCParams.dpi
    except:
      continue

    fX  = PAPER_SIZE[ strP ][ "X" ] * nDPI * MM_INCH
    fY  = PAPER_SIZE[ strP ][ "Y" ] * nDPI * MM_INCH

    for nSplitCount in range( nSize ):
      fX, fY  = calc_size( fX, fY )

    strFilenameP = "%s%d Port.spr"  % ( strP, nSize, )
    strFilenameL = "%s%d Land.spr" % ( strP, nSize, )
    export_file( strFilenameP, fX, fY, nDPI )
    export_file( strFilenameL, fY, fX, nDPI )

if( __name__ == "__main__" ):
  main()

# ---------------------------------------------------------------------- [EOF]

 

CherryPy を CGI として実行する方法

Python でウェブアプリケーションを作成する方法というのはたくさんあります。

私が好んで使用しているのは Pyramid(Pylons) や Zope なのですが、もっと小さなアプリケーションを作成、言ってしまえば1ファイルで完結してしまう様なものを作るにはちょっと大げさすぎる気もしています。

CGI として Python スクリプトに実行権限を与えてやるのが最も簡易的な方法なのですが、 Python の場合は WSGI という機能があり CGI で直接呼び出すよりも、より良い方法が存在しています。

CGIでの呼び出し
print "Content-type: text/plain"
print
print "Hello World!"
 wsgiでの呼び出し
#!/usr/bin/env python
import wsgiref.handlers

def application( environ, start_response ):
  start_response( "200 OK", [ ( "Content-Type", "text/plain" ) ] )
  return [ "Hello World!\n" ]

if( __name__ == "__main__" ):
  wsgiref.handlers.CGIHandler().run( application )

コードは長くなっていますが、 wsgi の方が単純な標準入出力を返すよりもプログラミングらしいかと思います。

CherryPy

と、前置きが長くなってしまいましたが、 CherryPy という Python モジュールで作成したアプリケーションを CGI で実行したい、という話です。

CherryPyの例
#!/usr/bin/env	python
# -*- coding: utf-8 -*-
#
# ------------------------------------------------------------------ import(s)
import	sys
sys.path.append( "./eggs/CherryPy-3.2.2-py2.7.egg" )
sys.path.append( "./eggs/Jinja2-2.7_devdev_20121117-py2.7.egg" )

import	wsgiref.handlers
import	cherrypy
import	jinja2

# ------------------------------------------------------------------ global(s)
oCJinja2Env	= None

# ------------------------------------------------------------------- class(s)

# ----------------------------------------------------------------------------
##
#
class Test( object ):

	@cherrypy.expose
	def index( self ):
		oCTemplate	= oCJinja2Env.get_template( "index.jinja2" )
		return( oCTemplate.render() )

if( __name__ == "__main__" ):
	global	oCJinja2Env

	oCJinja2Env	= jinja2.Environment( loader = jinja2.FileSystemLoader( "res/templates" ) )

	wsgiref.handlers.CGIHandler().run(
		cherrypy.Application( Test(), script_name = None, config = None )
	)

# ---------------------------------------------------------------------- [EOF]

上記のようにすると、 CherryPy を使用したウェブアプリケーションを CGI として実行する事が可能になります。

でも今は Amazon Web Services や Windows Azure などを利用するのが一般的かもしれませんので「CGI だけでなんとかしなければ…」といった場面は少ないかもしれません。

今日の NodeBox

単純に乱数をプロットしているだけのプログラムです。

自分が Python コードを記述するときは from を使用することがほとんどないのですけど nodebox.graphics とあちこちに記述するのはさすがに冗長すぎるかも。

#!/usr/bin/env	python
# ============================================================================
# ------------------------------------------------------------------ import(s)
import	sys
import	nodebox.graphics

# ------------------------------------------------------------------- param(s)

# ============================================================================
##
#
def draw( oCCanvas ):

	nodebox.graphics.background( 0, 0, 0, 0.1 )

	nodebox.graphics.stroke( 1, 1, 1, 1.0 )
	nodebox.graphics.strokewidth( 1 )
	nodebox.graphics.fill( 0, 0, 0, 0 )

	nodebox.graphics.rect( 160, 120, 320, 240 )

	nodebox.graphics.line(   0, 240, 640, 240 )
	nodebox.graphics.line( 320,   0, 320, 640 )

	nodebox.graphics.fill( 1, 1, 1, 1.0 )

	for n in range( 32 ):
		nX		= nodebox.graphics.random( 320.0 ) + 160
		nY		= nodebox.graphics.random( 240.0 ) + 120
		nSize	= nodebox.graphics.random(  32.0 )

		nodebox.graphics.push()
		nodebox.graphics.translate( nX, nY )
		nodebox.graphics.ellipse( 0, 0, nSize, nSize )
		nodebox.graphics.pop()

# ============================================================================
##
#
def main():

	nodebox.graphics.canvas.name	= "Demo02"
	nodebox.graphics.canvas.fps		= 60
	nodebox.graphics.canvas.size	= ( 640, 480 )
	nodebox.graphics.background( 1, 1, 1, 1 )

	nodebox.graphics.canvas.run( draw )

if( __name__ == "__main__" ):
	main()

# ---------------------------------------------------------------------- [EOF]

サークルと全く関係ない情報が増えてきたから、トップページからは外した方がよいかも…と思ったら、標準では記事のフィルタリングって出来なそう。

 

今日の NodeBox

NodeBox のチュートリアルを少しばかり書き換えただけなので、コード自体には面白みに欠けるかも。

#!/usr/bin/env	python
# ============================================================================
# ------------------------------------------------------------------ import(s)
import	sys
import	nodebox.graphics
import	nodebox.graphics.physics

# ------------------------------------------------------------------- param(s)
oCFlock			= None

# ============================================================================
##
#
def draw( oCCanvas ):
	global	oCFlock

	nodebox.graphics.background( 1, 1, 1, 0.25 )

	oCFlock.update( cohesion = 0.05 )

	nColor	= 0
	for boid in oCFlock:
		nodebox.graphics.push()
		nodebox.graphics.translate( boid.x, boid.y )

		fScale	= boid.depth * 2.0
		if( fScale < 0.75 ):
			fScale	= 0.75

		nodebox.graphics.scale( fScale )
		nodebox.graphics.rotate( boid.heading )

		nC	= nColor % 3
		if( nC == 0 ):
			nodebox.graphics.fill( 1, 0, 0, 0.25 )
		elif( nC == 1 ):
			nodebox.graphics.fill( 0, 1, 0, 0.25 )
		elif( nC == 2 ):
			nodebox.graphics.fill( 0, 0, 1, 0.25 )

		nodebox.graphics.ellipse( 0, 0, 16, 16 )
		nodebox.graphics.pop()

		nColor	+= 1

# ============================================================================
##
#
def main():
	global	oCFlock

	oCFlock				= nodebox.graphics.physics.Flock( 64, 0, 0, 640, 480 )
	oCFlock.sight	= 640

	nodebox.graphics.canvas.name	= "Demo01"
	nodebox.graphics.canvas.fps		= 20
	nodebox.graphics.canvas.size	= ( 640, 480 )
	nodebox.graphics.canvas.run( draw )

if( __name__ == "__main__" ):
	main()

# ---------------------------------------------------------------------- [EOF]

単純な描画を行うにはかなり適しているかも。

 

NodeBox for OpenGL

pyglet について調べていたら NodeBox for OpenGL というライブラリがあるのを知りました。 NodeBox でやる事を Python ライブラリの形で実装しているようで、 pyglet を直接使うよりも便利そう。

ちなみに NodeBox というのは、ビジュアルプログラミング環境の一つで、データを可視化したり VJ 素材を作成したりするのに利用するソフトウェアです。(Mac でいうと Quartz Composer が近いかもしれません。)

というわけで、まずは pyglet と NodeBox for OpenGL による開発環境を用意することに。(ちなみに手元の環境は MacBookPro)

virtualenv, virtualenvwrapper

単純にモジュールをインストールしてしまっても良いのですけど、 easy_install や pip を行うと、 Python のライブラリフォルダにモジュールが入ってしまいます。

一時的な環境構築の場合は、環境を分離したいところ。

というわけで virtualenv を入れる事で環境を切り替えられるようにします。

easy_install virtualenv
easy_install virtualenvwrapper

インストール完了後は、.bash_profile あたりに以下の様な記述を追記します。

export WORKON_HOME=~/vEnv
source /usr/local/bin/virtualenvwrapper.sh

準備が出来たら、 新しくターミナルを開くか bash .bash_profile 等で設定を反映させて、作成した環境の名前を指定してやります。

Mizunagis-MacBook-Pro:~ mizunagi$ mkvirtualenv --system-site-packages NodeBox
New python executable in NodeBox/bin/python
Installing setuptools............done.
Installing pip...............done.
virtualenvwrapper.user_scripts creating /Users/mizunagi/vEnv/NodeBox/bin/predeactivate
virtualenvwrapper.user_scripts creating /Users/mizunagi/vEnv/NodeBox/bin/postdeactivate
virtualenvwrapper.user_scripts creating /Users/mizunagi/vEnv/NodeBox/bin/preactivate
virtualenvwrapper.user_scripts creating /Users/mizunagi/vEnv/NodeBox/bin/postactivate
virtualenvwrapper.user_scripts creating /Users/mizunagi/vEnv/NodeBox/bin/get_env_details
(NodeBox)Mizunagis-MacBook-Pro:~ mizunagi$

次回の利用時からは、 workon NodeBox と入力する事で環境を切り替える事が出来ます。

pyglet

自分の環境 ( Mac OS X 10.7.5 ) では、easy_install pyglet でインストールを行っても OSError が発生してしまうため、手動でインストールする事にします。

以下のURLを参考にしたのですが、そのままだと setup.py になぜか cocoa が指定されていないため、インストールしても動作しません。( 2012-10-27現在)

ですので、 hg checkout cocoa-port を行った後に setup.py を開き、 pyglet.window.cocoa を Packages に追加しておきます。

そんなこんなでひとまず動作するようになりました。(GUIモジュールが正常に動作していないようにみえるけど…)

2012-10-28追記

NodeBox for OpenGL の GUI モジュールではなくて、 pyglet の input に問題があるみたい。

バックアップを兼ねて作成した egg ファイルはこちら。

nodebox_opengl-1.7-py2.7.egg

[wpdm_file id=1]

pyglet-1.2dev-py2.7.egg

[wpdm_file id=2]