2009年11月13日 星期五

Qt 專案管理

假設有一個母專案MyWholeApp
其中有一個子專案在子資料夾DummyDlg中

在DummyDlg中
建立一檔案名為DummyDlg.pri
其中內容為
FORMS += $$PWD/dummydlg.ui
HEADERS += $$PWD/dummydlg.h
SOURCES += $$PWD/dummydlg.cpp

修改專案檔DummyDlg.pro
TEMPLATE = app
DEPENDPATH += .
INCLUDEPATH += .
include(DummyDlg.pri)
# Input
SOURCES += main.cpp

這樣子專案就建立完成了

至於母專案如何利用子專案
WholeApp.pro
TEMPLATE = app
DEPENDPATH += . DummyDlg
INCLUDEPATH += . DummyDlg
include(DummyDlg/DummyDlg.pri)
# Input
FORMS += OtherDlg.ui
HEADERS += OtherDlg.h
SOURCES += OtherDlg.cpp
WholeApp.cpp

2009年10月21日 星期三

將widget存成圖檔

#include <QApplication>
#include <QLabel>
#include <QTextCodec>
#include <QRgb>

int main(int argc, char *argv[]) {
QApplication app(argc, argv);

QTextCodec *codec = QTextCodec::codecForName("Big5-ETen");

QLabel *label = new QLabel;
label->setText(codec->toUnicode("<center><h1>Qt4 學習筆記</h1>~yycking.blogspot.com</center>"));

label->setStyleSheet("QLabel { background-color: white; }");
label->adjustSize();

label->show();

QImage image = QPixmap::grabWidget(label).toImage();
image = image.convertToFormat(QImage::Format_Indexed8);

QVector<QRgb> indexs=image.colorTable();
for (int i = 0; i < indexs.size(); ++i) {
int r=qRed(indexs[i]);
int g=qGreen(indexs[i]);
int b=qBlue(indexs[i]);
int a=qGray(indexs[i]);
indexs[i]=qRgba(r,g,b,255-a);
}

image.setColorTable(indexs);
image.save("somefile.PNG");

return app.exec();
}



這個方法只簡易的將Gray代替alpha而已的圖

2009年10月9日 星期五

用Qt designer自訂slots


開啟一個新專案


選擇Widget

用Qt Designer開啟ui檔


設計界面


按F4將spinbox拖到widget


打開連線設定


選左邊的訊號,再點又右邊的編輯按鈕


點擊信號槽下的加號,填入新增slot,連續二個確定回到主界面


按F3將widget拖到textEdit


按編輯按鈕


按信號下的加號,增加新的singal


選擇剛建立的signal

存檔,回到Qt creator
修改檔案
widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui/QWidget>

namespace Ui
{
class Widget;
}

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = 0);
~Widget();

public slots:
void setFontSize(int);

signals:
void fontSizeChanged(qreal);

private:
Ui::Widget *ui;
};

#endif // WIDGET_H


widget.cpp
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
}

Widget::~Widget()
{
delete ui;
}

void Widget::setFontSize(int size){
emit fontSizeChanged((qreal)size);
}



如何將Qt中的assistant,designer和linguist中文化

將C:\Qt\2009.04\qt\translations
內的所有檔案拉到
C:\Qt\2009.04\qt\bin\lrelease.exe
即可

2009年6月21日 星期日

Split Brain Syndrome: 誰是遊戲開發者們心目中的英雄?

以下內容轉貼至http://malated.blogspot.com/2009/06/blog-post.html


國外Develop Magazine找了約9000位遊戲開發者票選遊戲界的「終極開發英雄!」
不囉唆,我後面一一介紹,以下是票選的top 10:

第十名:Jonathan Blow
一定很多人對這名字很陌生,不過說出他的作品我想大家一定...也很陌生,他的作品在台灣不紅,不過在國外相當有名,叫作Braid的橫向捲軸遊戲,這款作品在2006年獲得最佳獨立製作的遊戲,無論創意、風格、遊戲感都相當優秀,而且相當精緻且富饒趣味,我也相當喜歡這款作品,讓我消磨了不少美好得假日時光,我想他能進top 10也因為不少遊戲開發者都嚮往像他那樣能夠獨立開發這麼優秀的作品吧。

File:Jonathan Blow.jpg

第九名:Michael Morhaime
我想他不用多說了,Blizzard的創辦人,至於他參與的作品,我想也不必多說了,暗黑、魔獸、星海...都是遊戲界的經典,也都是不少人最愛的遊戲作品,包括我。

File:Mike Morhaime.jpg

第八名:
松浦雅也
松浦雅也,節奏遊戲之父,如果對節奏遊戲熟悉的話一定知道PaRappa這款經典的節奏遊戲,這也是松浦雅也的代表作PaRappa帶動了節奏遊戲的熱潮,造成後來的節奏遊戲盛世,曾經一段時間節奏遊戲幾乎佔滿遊戲發售列表,即時現在像吉他英雄、Rockband這些節奏遊戲也是大眾的最愛,不過我永遠忘不了RaRappa系列對我造成的衝擊,「Dojocasinoit's all in the mind, 洋蔥師夫的這句話幾乎烙在我的腦子裡。

http://www.gamehome.tv/Article/UploadFiles/200812/20081202094224402.jpg

第七名:David Braben
我想知道他的人更少了,他是的代表作品是Zarch(又稱Virus),算是第一款塊狀3D遊戲,早期的3D遊戲都是線條構成的,Zarch的出現算是一大創舉,他另一個知名星際空戰遊戲Elite,我想不少老前輩應該不陌生,目前參與the outsider的開發,還有他計畫想要開發Elite 4 的太空科幻MMORPG,如果你對星際空戰遊戲熟悉的話,幾乎都會認識他,可惜我對星際空戰遊戲不熟阿!

http://cache.kotaku.com/assets/resources/2007/09/giantbraben.jpg

第六名: Peter Molyneux
我最喜歡的老牌遊戲製作人之一,他的作品都極富創意跟遊戲性,如果你不認識他,說幾個我最喜歡玩的作品你就知道了,像地城守護者、善與惡、神鬼寓言都是他的經典作品,今年E3他也為新作品Project Milo出來宣傳,這款語意與攝影機配合的人工智慧遊戲又再次吸引大眾的目光。


第五名:Sid Meier
我想喜歡玩策略遊戲的人絕對認識他,他的名字幾乎是策略遊戲的代名詞,尤其是文明帝國系列,這是一款讓人廢寢忘時的遊戲,這一點都不誇張,我在玩文明系列時,好幾次都是早上一起床就玩到三更半夜,此外他的鐵路大亨、大海盜也都是相當優秀的作品。

File:Sid Meier cropped.jpg

第四名:
Dave Jones
Rockstar North這家遊戲公司的創辦人之一,他的代表作品就是極富爭議的GTA系列,但是很多人不知道,他早期也曾經參與過經典老作品旅鼠系列的開發,GTA雖然是現在暴力遊戲的代名詞,但是他的自由度與遊戲性卻帶給遊戲界一股新的氣象,也影響不少後來的遊戲仿效他的自由暴力風格,像教父系列、人中之龍..等等。

http://www.yxbns.com/renwu/jones/dave_jones_pcu.jpg

第三名:Will Wright
如果策略遊戲的代名詞是sid meier,那麼模擬遊戲的代名詞絕對是will wright,像模擬城市、模擬地球、模擬螞蟻、模擬市民、到最近的SPORE,都是受歡迎的經典作品,尤其是模擬城市,有些都市計畫的課程還會拿這遊戲當教材,可惜他老兄前不久離開EA跑去Stupid Fun Club這家機器人工作室,想要開發新型態的娛樂,希望他能夠搞出新奇的玩意。




第二名:John Carmack
這傢伙14歲就跑去偷學校的蘋果電腦而被逮捕過,後來成為id software的創辦人,這家公司幾乎是fps的代名詞,毀滅戰士、雷神之錘、重返德軍總部這些經典的FPS都是他的作品,這些作品也是我得最愛,我永遠忘不了我第一次玩重返德軍總部玩到因為3D暈而嘔吐,但是我卻還是一直想玩,在邊玩邊吐的情況下,我卻逐漸對3D暈免疫了,感謝這款作品讓我能夠享受往後FPS遊戲的美妙。




第一名:宮本茂
誰又能料到當初只是想當畫家的嬉皮,現在卻影響整個電玩界這麼深厚,馬利歐、大金剛、薩爾達、銀河戰士...等等經典美好得遊戲都是出自他手,事實上你可發覺他開發的遊戲題材之廣,幾乎快包涵各個類型,他的作品大多包涵藝術與遊戲性又
 兼具市場價值,這幾年主導開發的wii也給遊戲界一個震撼彈,毫無疑問的,這個第一名實至名歸。

File:Shigeru Miyamoto cropped.jpg

2009年4月7日 星期二

Git 原始碼管理

今天在研究Git ,發現真的比Subversion好用太多了。
不過可惜的是只支援Linux 和Mac,windows要用msysgit。

剛好找到一篇非常詳細的中文教學
http://www.qweruiop.org/nchcrails/posts/49

2009年4月2日 星期四

Qt 啟動畫面(Splash Screen)

#include <QtGui/QApplication>
#include <QMainWindow>
#include <QPixmap>
#include <QBitmap>
#include <QSplashScreen>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

//啟動畫面
QSplashScreen *splash = new QSplashScreen;
//顯示圖片
QPixmap px(":/res/logo.png");
splash->setPixmap(px);
//遮罩圖
splash->setMask(px.mask());
splash->show();

QMainWindow w;
w.show();

//如果w顯示出來的話,才關閉啟動畫面
splash->finish(&w);
delete splash;

return a.exec();
}

2009年4月1日 星期三

Qt Opengl QMainWindow

glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QGLWidget>

class GLWidget : public QGLWidget
{
public:
GLWidget(QWidget *parent);

~GLWidget();
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
};

#endif // GLWIDGET_H

glwidget.cpp
#include "glwidget.h"

GLWidget::GLWidget(QWidget *parent)
: QGLWidget(parent)
{}

GLWidget::~GLWidget()
{}

void GLWidget::initializeGL()
{
qglClearColor( Qt::black );
}

void GLWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT );

glLoadIdentity();
qglColor( Qt::white );
glBegin(GL_TRIANGLES);
glVertex3f( 0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f,-1.0f, 0.0f);
glVertex3f( 1.0f,-1.0f, 0.0f);
glEnd();
}

void GLWidget::resizeGL(int w, int h)
{
h = h < 1 ? 1 : h;
glViewport( 0, 0, (GLint)w, (GLint)h );
}


mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui/QMainWindow>
#include <QMenuBar>
#include <QAction>
#include <QApplication>
#include "glwidget.h"
class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
GLWidget *w;
};

#endif // MAINWINDOW_H

mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
w=new GLWidget(this);
setCentralWidget(w);

QMenu *fileMenu = new QMenu("&File");
fileMenu->addAction("&Close", qApp, SLOT(quit()), Qt::CTRL + Qt::Key_Q);
menuBar()->addMenu(fileMenu);
}

MainWindow::~MainWindow()
{

}

main.cpp
#include <QtGui/QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

opengl.pro
QT += opengl
TARGET = opengl
TEMPLATE = app
SOURCES += main.cpp \
mainwindow.cpp \
glwidget.cpp
HEADERS += mainwindow.h \
glwidget.h

Qt Opengl

glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QGLWidget>

class GLWidget : public QGLWidget
{
public:
GLWidget();

~GLWidget();
void initializeGL();
void paintGL();
void resizeGL(int w, int h);
};

#endif // GLWIDGET_H

glwidget.cpp
#include "glwidget.h"

GLWidget::GLWidget()
: QGLWidget()
{}

GLWidget::~GLWidget()
{}

void GLWidget::initializeGL()
{
qglClearColor( Qt::black );
}

void GLWidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT );

glLoadIdentity();
qglColor( Qt::white );

glBegin(GL_TRIANGLES);
glVertex3f( 0.0f, 1.0f, 0.0f);
glVertex3f(-1.0f,-1.0f, 0.0f);
glVertex3f( 1.0f,-1.0f, 0.0f);
glEnd();
}

void GLWidget::resizeGL(int w, int h)
{
h = h < 1 ? 1 : h;
glViewport( 0, 0, (GLint)w, (GLint)h );
}


main.cpp
#include <QApplication>
#include "glwidget.h"

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

GLWidget w;
w.resize(800, 600);
w.show();

return app.exec();
}


gl.pro
QT += opengl
TARGET = gl
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp \
glwidget.cpp
HEADERS += glwidget.h

2009年3月31日 星期二

Qt menu for Mac


#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QAction>

int main(int argc, char *argv[]) {
QApplication app(argc, argv);

QMainWindow *mainWindow = new QMainWindow;
mainWindow->setWindowTitle("QMainWindow");

QMenu *fileMenu = new QMenu("File");
fileMenu->addAction("Quit", &app, SLOT(quit()), Qt::CTRL + Qt::Key_Q);
mainWindow->menuBar()->addMenu(fileMenu);

mainWindow->show();

return app.exec();
}


#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QAction>

int main(int argc, char *argv[]) {
QApplication app(argc, argv);

QMainWindow *mainWindow = new QMainWindow;
mainWindow->setWindowTitle("QMainWindow");

QMenu *fileMenu = new QMenu("File");
fileMenu->addAction("Close", &app, SLOT(quit()), Qt::CTRL + Qt::Key_Q);
mainWindow->menuBar()->addMenu(fileMenu);

mainWindow->show();

return app.exec();
}


一直覺得很奇怪,為甚麼就是叫Quit的item不能加入menubar
後來才發現這是Qt特意為mac設定的
官方說明請看這裡

2009年3月12日 星期四

如何在xcode中設定圖示

之前已經說過如何產生圖示,像在要說的是在xcode中如何設定。
在專案的Targets中,打開info。
在info的Properties中,設定Icon檔的名字。在專案的Targets中,用add->existing files將icon檔加入resouse。

現在,只要編譯後,app檔案就會自動產生圖示了。

2009年3月3日 星期二

picasa api

這是一個簡單的picasa工具,google有簡單的使用教學。
這個程式,主要將就的相本刪除,以新的相本替換。
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.net.URL;

import com.google.gdata.client.photos.*;
import com.google.gdata.data.*;
import com.google.gdata.data.media.*;
import com.google.gdata.data.photos.*;

/**
*
* @author iMac
*/
public class picasa {

public static void main(String[] args) {
try {
//連結服務端
PicasawebService myService = new PicasawebService("專案名稱(隨便你)");
myService.setUserCredentials("帳號", "密碼");

//相本代碼
String id = "";

//相本代碼存於picasa.id
File file = new File("picasa.id");
if (file.exists()) {
//讀出相本代碼
BufferedReader buffer = new BufferedReader(new FileReader(file));
id = buffer.readLine();

//刪除picasaweb的相本
URL albumPostUrl = new URL("http://picasaweb.google.com/data/entry/api/user/你的大名/albumid/" + id);
AlbumEntry insertedEntry = myService.getEntry(albumPostUrl, AlbumEntry.class);
insertedEntry.delete();
buffer.close();
}

//移至個人資料夾
URL feedUrl = new URL("http://picasaweb.google.com/data/feed/api/user/你的大名?kind=album");

//建立新相本
AlbumEntry myAlbum = new AlbumEntry();
//相本名稱
myAlbum.setTitle(new PlainTextConstruct("相本名稱"));
//相本描述
myAlbum.setDescription(new PlainTextConstruct("相本描述"));
//相本為公開性質
myAlbum.setAccess("public");
//登入伺服器,建立相本
AlbumEntry insertedEntry = myService.insert(feedUrl, myAlbum);

//截取新相本代碼
String s = insertedEntry.getId();
int index = s.lastIndexOf("/");
id = s.substring(index + 1);

//移至新相本
URL albumPostUrl = new URL("http://picasaweb.google.com/data/feed/api/user/你的大名/albumid/" + id);

//建立新照片
PhotoEntry myPhoto = new PhotoEntry();
//照片名稱
myPhoto.setTitle(new PlainTextConstruct("照片名稱"));
//照片描述
myPhoto.setDescription(new PlainTextConstruct("照片描述"));
myPhoto.setClient("myClientName");
//設定上傳檔案
MediaFileSource myMedia = new MediaFileSource(new File("2.JPG"), "image/jpeg");
myPhoto.setMediaSource(myMedia);
//登入伺服器,上傳
PhotoEntry returnedPhoto = myService.insert(albumPostUrl, myPhoto);

//儲存新相本代碼
BufferedWriter buffer = new BufferedWriter(new FileWriter(file));
buffer.write(id);
buffer.close();
} catch (Exception e) {
System.out.println(e);
}
}
}

2009年2月19日 星期四

java副檔名過濾器

import java.io.File;
import java.io.FileFilter;

public class ExtensionFileFilter implements FileFilter {

private String[] extension;


public ExtensionFileFilter(String[] s) {
extension=s;
}

public boolean accept(File file) {
if (file.isDirectory()) {
return false;
}

String name = file.getName();
// find the last
int index = name.lastIndexOf(".");
if (index == -1) {
return false;
} else if (index == name.length() - 1) {
return false;
} else{
for(int i=0;i<extension.length;i++){
boolean b=extension[i].equals(
name.substring(index + 1).toLowerCase());
if(b){
return true;
}
}
return false;
}
}
}
用法
String[] fileExtension = {"jpg", "jpeg"};
files = path.listFiles(new ExtensionFileFilter(fileExtension));

關於java中初始化界面中jProgressBar設計

//設定初使化的JFrame
frame = new InitFrame();
Thread thread = new Thread(new Runnable() {

public void run() {
//在這裡做設定主畫面的設定
for(){
frame.progress.setValue(i);
}

}
});
thread.start();

JFrame置中

//要放在pack,setVisible之後。
frame.setLocationRelativeTo(null);

2009年2月13日 星期五

Script language~Lua和Python比較




Lua

Python

效能

普通
檔案



功能和套件普通

齊全
使用率

22名(0.46%)

7名(4.7%)

版權宣告

MIT

Python license

安裝需求-MAC

不用,可嵌入主程式MAC內建

安裝需求-WINDOWS

不用,可嵌入主程式須安裝Python

更新疑慮

無更新問題

可能有







GUI-wxWidget



安裝需求-MAC須安裝wxLua,wxWidgetMAC內建
安裝需求-WINDOWS須安裝wxLua,wxWidget須安裝wxPython,wxWidget
附註

wxLua和wxWidget有整合版wxPython和wxWidget要分開安裝
更新疑慮無更新問題可能有
其他問題



wxPython目前不支援Python 3k

(Python 3k向下不相容)







ftp功能

須LuaSocket

內建


2009年2月12日 星期四

Embedded Python + wxPython + OpenGL

先看一下wxPython官網關於opengl的教學
try:
import wx
from wx import glcanvas
except ImportError:
raise ImportError, "Required dependency wx.glcanvas not present"

try:
from OpenGL.GL import *
except ImportError:
raise ImportError, "Required dependency OpenGL not present"

class GLFrame(wx.Frame):
"""A simple class for using OpenGL with wxPython."""

def __init__(self, parent, id, title, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name='frame'):
#
# Forcing a specific style on the window.
# Should this include styles passed?
style = wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE

super(GLFrame, self).__init__(parent, id, title, pos, size, style, name)

self.GLinitialized = False
attribList = (glcanvas.WX_GL_RGBA, # RGBA
glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered
glcanvas.WX_GL_DEPTH_SIZE, 24) # 24 bit

#
# Create the canvas
self.canvas = glcanvas.GLCanvas(self, attribList=attribList)

#
# Set the event handlers.
self.canvas.Bind(wx.EVT_ERASE_BACKGROUND, self.processEraseBackgroundEvent)
self.canvas.Bind(wx.EVT_SIZE, self.processSizeEvent)
self.canvas.Bind(wx.EVT_PAINT, self.processPaintEvent)

#
# Canvas Proxy Methods

def GetGLExtents(self):
"""Get the extents of the OpenGL canvas."""
return self.canvas.GetClientSize()

def SwapBuffers(self):
"""Swap the OpenGL buffers."""
self.canvas.SwapBuffers()

#
# wxPython Window Handlers

def processEraseBackgroundEvent(self, event):
"""Process the erase background event."""
pass # Do nothing, to avoid flashing on MSWin

def processSizeEvent(self, event):
"""Process the resize event."""
if self.canvas.GetContext():
# Make sure the frame is shown before calling SetCurrent.
self.Show()
self.canvas.SetCurrent()

size = self.GetGLExtents()
self.OnReshape(size.width, size.height)
self.canvas.Refresh(False)
event.Skip()

def processPaintEvent(self, event):
"""Process the drawing event."""
self.canvas.SetCurrent()

# This is a 'perfect' time to initialize OpenGL ... only if we need to
if not self.GLinitialized:
self.OnInitGL()
self.GLinitialized = True

self.OnDraw()
event.Skip()

#
# GLFrame OpenGL Event Handlers

def OnInitGL(self):
"""Initialize OpenGL for use in the window."""
glClearColor(1, 1, 1, 1)

def OnReshape(self, width, height):
"""Reshape the OpenGL viewport based on the dimensions of the window."""
glViewport(0, 0, width, height)

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-0.5, 0.5, -0.5, 0.5, -1, 1)

glMatrixMode(GL_MODELVIEW)
glLoadIdentity()

def OnDraw(self, *args, **kwargs):
"Draw the window."
glClear(GL_COLOR_BUFFER_BIT)

# Drawing an example triangle in the middle of the screen
glBegin(GL_TRIANGLES)
glColor(0, 0, 0)
glVertex(-.25, -.25)
glVertex(.25, -.25)
glVertex(0, .25)
glEnd()

self.SwapBuffers()

app = wx.PySimpleApp()
frame = GLFrame(None, -1, 'GL Window')
frame.Show()

app.MainLoop()
app.Destroy()
但是不能執行,這是因為mac中沒有pyOpenGL。而我們也不想安裝pyOpenGL,所以先把opengl的部份換掉
try:
import wx
import mymodule
from wx import glcanvas
except ImportError:
raise ImportError, "Required dependency wx.glcanvas not present"

class GLFrame(wx.Frame):
"""A simple class for using OpenGL with wxPython."""

def __init__(self, parent, id, title, pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name='frame'):
#
# Forcing a specific style on the window.
# Should this include styles passed?
style = wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE

super(GLFrame, self).__init__(parent, id, title, pos, size, style, name)

self.GLinitialized = False
attribList = (glcanvas.WX_GL_RGBA, # RGBA
glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered
glcanvas.WX_GL_DEPTH_SIZE, 24) # 24 bit

#
# Create the canvas
self.canvas = glcanvas.GLCanvas(self, attribList=attribList)

#
# Set the event handlers.
self.canvas.Bind(wx.EVT_ERASE_BACKGROUND, self.processEraseBackgroundEvent)
self.canvas.Bind(wx.EVT_SIZE, self.processSizeEvent)
self.canvas.Bind(wx.EVT_PAINT, self.processPaintEvent)

#
# Canvas Proxy Methods

def GetGLExtents(self):
"""Get the extents of the OpenGL canvas."""
return self.canvas.GetClientSize()

def SwapBuffers(self):
"""Swap the OpenGL buffers."""
self.canvas.SwapBuffers()

#
# wxPython Window Handlers

def processEraseBackgroundEvent(self, event):
"""Process the erase background event."""
pass # Do nothing, to avoid flashing on MSWin

def processSizeEvent(self, event):
"""Process the resize event."""
if self.canvas.GetContext():
# Make sure the frame is shown before calling SetCurrent.
self.Show()
self.canvas.SetCurrent()

size = self.GetGLExtents()
self.OnReshape(size.width, size.height)
self.canvas.Refresh(False)
event.Skip()

def processPaintEvent(self, event):
"""Process the drawing event."""
self.canvas.SetCurrent()

# This is a 'perfect' time to initialize OpenGL ... only if we need to
if not self.GLinitialized:
self.OnInitGL()
self.GLinitialized = True

self.OnDraw()
event.Skip()

#
# GLFrame OpenGL Event Handlers

def OnInitGL(self):
"""Initialize OpenGL for use in the window."""
mymodule.OnInitGL()

def OnReshape(self, width, height):
"""Reshape the OpenGL viewport based on the dimensions of the window."""
mymodule.OnReshape(width,height)

def OnDraw(self, *args, **kwargs):
"Draw the window."
glClear(GL_COLOR_BUFFER_BIT)

# Drawing an example triangle in the middle of the screen
mymodule.OnDraw()

self.SwapBuffers()

app = wx.PySimpleApp()
frame = GLFrame(None, -1, 'GL Window')
frame.Show()

app.MainLoop()
app.Destroy()
這時你因該注意到mymodule這個未知的Module吧。當然現在還不能執行。
我們必須在C的檔案中,定義mymodule(詳細說明請參閱thinker的為 Embedded Python 擴充 Submodules)。
PyObject *OnInitGL(PyObject *self) {
glClearColor(1, 1, 1, 1);
return PyString_FromString("GL Init");
}
PyObject *OnReshape(PyObject *self, PyObject *args) {
int a, b;

if(!PyArg_ParseTuple(args, "ii", &a, &b)) return NULL;
glViewport(0, 0, a, b);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-0.5, 0.5, -0.5, 0.5, -1, 1);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
return PyString_FromString("Reshape");
}
PyObject *OnDraw(PyObject *self) {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(0, 0, 0);
glVertex2f(-0.25, -0.25);
glVertex2f(0.25, -0.25);
glVertex2f(0, 0.25);
glEnd();
return PyString_FromString("Draw");
}

PyMethodDef methods[] = {
{"OnInitGL", (PyCFunction)OnInitGL, METH_NOARGS, "OnInitGL document"},
{"OnReshape", (PyCFunction)OnReshape, METH_VARARGS, "OnReshape document"},
{"OnDraw", (PyCFunction)OnDraw, METH_NOARGS, "OnDraw document"},
{ NULL, NULL, 0, NULL}
};
在main()中加入
Py_InitModule("mymodule", methods);
整個主程式
//將SCRIPT_PATH設定成py檔和執行檔的相對路徑
#define SCRIPT_PATH "/"
//設定mymodule
PyObject *OnInitGL(PyObject *self) {
glClearColor(1, 1, 1, 1);
return PyString_FromString("GL Init");
}
PyObject *OnReshape(PyObject *self, PyObject *args) {
int a, b;

if(!PyArg_ParseTuple(args, "ii", &a, &b)) return NULL;
glViewport(0, 0, a, b);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-0.5, 0.5, -0.5, 0.5, -1, 1);

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
return PyString_FromString("Reshape");
}
PyObject *OnDraw(PyObject *self) {
glClear(GL_COLOR_BUFFER_BIT);
glBegin(GL_TRIANGLES);
glColor3f(0, 0, 0);
glVertex2f(-0.25, -0.25);
glVertex2f(0.25, -0.25);
glVertex2f(0, 0.25);
glEnd();
return PyString_FromString("Draw");
}

PyMethodDef methods[] = {
{"OnInitGL", (PyCFunction)OnInitGL, METH_NOARGS, "OnInitGL document"},
{"OnReshape", (PyCFunction)OnReshape, METH_VARARGS, "OnReshape document"},
{"OnDraw", (PyCFunction)OnDraw, METH_NOARGS, "OnDraw document"},
{ NULL, NULL, 0, NULL}
};
int main(int argc, char* argv[]) {
char pch[1024]={0};
char scriptpath[1024]={0};
int i=strrchr(argv[0],'/')-argv[0];
strncpy (pch,argv[0],i);
strcpy(scriptpath,SCRIPT_PATH);
strcat(pch,scriptpath);
strcat(pch,"/gui.py");
FILE *fp;

Py_Initialize();
//啓動mymodule
Py_InitModule("mymodule", methods);
fp = fopen(pch, "r");
PyRun_SimpleFile(fp, "gui.py");
fclose(fp);

Py_Finalize();
}
編譯時記得連結 -lpython2.5 -framework OpenGL

Embedded Python + wxPython

上一篇依樣,只是這次是用另依種Embedded的方法
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import wx
app = wx.PySimpleApp()
frame = wx.Frame(None, -1, u"你好, 世界")
frame.Show(True)
app.MainLoop()
要注意的是,這次是用file open所以設定PYTHONPATH是沒用得。要改的是open file的路徑。
#include <Python2.5/Python.h>
#include <stdio.h>
//將SCRIPT_PATH設定成py檔和執行檔的相對路徑
#define SCRIPT_PATH "/"
int main(int argc, char* argv[]) {
char pch[1024]={0};
char scriptpath[1024]={0};
int i=strrchr(argv[0],'/')-argv[0];
strncpy (pch,argv[0],i);
strcpy(scriptpath,SCRIPT_PATH);
strcat(pch,scriptpath);
strcat(pch,"/gui.py");
FILE *fp;

Py_Initialize();
fp = fopen(pch, "r");
PyRun_SimpleFile(fp, "gui.py");
fclose(fp);

Py_Finalize();
}
如果你遇到如下的問題
This program needs access to the screen.
Please run with 'pythonw', not 'python', and only when you are logged
in on the main display of your Mac.
請參考,http://yycking.blogspot.com/2009/02/mac_12.html

Embedded Python

我是依thinker的文章(Embedded Python 之一)來作學習。
連結:http://heaven.branda.to/~thinker/GinGin_CGI.py/show_id_doc/234
原始的程式碼
#foo.py
def hello(a, b):
return a + b
#include <Python.h>
#include <stdio.h>

int main() {
PyObject *foo;
PyObject *hello, *ro;
int r;

Py_Initialize();
foo = PyImport_ImportModule("foo");
hello = PyObject_GetAttrString(foo, "hello");
ro = PyObject_CallFunction(hello, "ii", 15, 20);
r = PyInt_AsLong(ro);

printf("result is %d\n", r);

Py_DECREF(ro);
Py_DECREF(hello);
Py_DECREF(foo);
Py_Finalize();
}
因為執行環境是mac
所以先將
#include <python.h>
#include <python2.5/python2.5>
編譯時記得連結 -lpython2.5
編譯完成候執行時,一直說Bus error。
這是因為PYTHONPATH的問題。
解決發法如下。
Py_Initialize();
//只要利用PySys_SetPath,就可以設定PYTHONPATH
PySys_SetPath("py檔路徑");
foo = PyImport_ImportModule("foo");
hello = PyObject_GetAttrString(foo, "hello");
ro = PyObject_CallFunction(hello, "ii", 15, 20);
r = PyInt_AsLong(ro);
但是問題來了:
我在terminal下./tool(我的執行檔)可以正常工作,但是在tool的圖示點二下,卻在PyObject_GetAttrString的地方,顯示bus error。這是為甚麼?
Thinker said ..
可能和 current working directory 有關或環境變數有關係,請檢查透過桌面執行時的 current working directory 和相關的環境變數。
(在此,再次感謝thinker的協助)
所以還是老問題PYTHONPATH,只好在程式執行是動態來判斷路徑。
整個主程式如下:
#include <Python2.5/Python.h>
#include <stdio.h>
//將SCRIPT_PATH設定成py檔和執行檔的相對路徑
#define SCRIPT_PATH "/"
int main(int argc, char* argv[]) {
char pch[1024]={0};
char scriptpath[1024]={0};
int i=strrchr(argv[0],'/')-argv[0];
strncpy (pch,argv[0],i);
strcpy(scriptpath,SCRIPT_PATH);
strcat(pch,scriptpath);

PyObject *foo;
PyObject *hello, *ro;
int r;

Py_Initialize();
PySys_SetPath(pch);
foo = PyImport_ImportModule("foo");
hello = PyObject_GetAttrString(foo, "hello");
ro = PyObject_CallFunction(hello, "ii", 15, 20);
r = PyInt_AsLong(ro);

printf("result is %d\n", r);

Py_DECREF(ro);
Py_DECREF(hello);
Py_DECREF(foo);
Py_Finalize();
}

在mac中,編譯opengl注意事項

發生問題

error: GL/gl.h: No such file or directory

error: GL/glu.h: No such file or directory


解決辦法



#include <GL/gl.h>

#include <GL/glu.h>

改成

#include <OpenGL/gl.h>

#include <OpenGL/glu.h>

編譯時注意-lGLU -lGL -framework OpenGL

在mac中,建立應用套件

建立一個叫tool的資料夾。
之後,在tool資料夾裡,建立一個叫Contents的資料夾。
再建立MacOS和Resource二個資料夾。
將執行檔丟入MacOS資料夾。
將圖示丟入 Resource資料夾。再打開Developer->Applicationis->Utlities的Property List Sditor依圖說明,
先建立二個item分別為Executable file和Icin file。
再將tool和tool.icns分別改成你的執行檔名和圖示名。
再將檔案存在剛剛建立的Contents中,命名為info.plist

最後將一開始建立的tool資料夾,重新命名為tool.app就完成了。

在mac中,建立應用程式的圖示(icns)


要先安裝xcode(在第二片安裝光碟中),之後在用Developer->Applicationis->Utlities的Icon Composer編輯圖檔,就可存成icns檔

mac畫面截取

1.蘋果鍵+Shift+3 截取全螢幕
2.蘋果鍵+Shift+4 截取選取範圍
3.蘋果鍵+Shift+4+空白 截取視窗

2009年2月2日 星期一

[轉貼]獨立遊戲開發者的獨孤九訣

以下文章轉貼至猴子靈藥。我個人,覺得翻譯的很用心,所以貼出來和大家分享。

原文出處:Nine Paths To Indie Game Greatness


本文的作者,是一位曾製作過 PC、Console 以及 Mobile 平台遊戲的專業開發者。自從他以《Quake》內附的編輯器,製作出第一個遊戲關卡的那一刻起,他就領略到了創造遊戲的那種無上樂趣,也因此下定決心開始學習如何製作遊戲。然而當他在遊戲業界中,經歷了六年的工作生涯後,反而開始質疑自己是否擁有繼續開發遊戲的渴望。就在他決心離開遊戲業界,去嘗試其他的事物之後,反倒從如新生嫩芽般的獨立遊戲社群中,再次重新找回自己對於創造遊戲的熱情


在經歷過許多已經關門倒閉的遊戲公司以後,他充分地瞭解,開發商業遊戲需要非常廣泛的作用力與各種面向的考量,耗費於其中的時間,甚至遠超過投注於實際遊戲開發程序的時間。越瞭解這項事實,他就越加覺得自己已經游離了當初那個使他感到興奮雀躍的遊戲開發根源。他不禁懷疑,世界上是否有任何地方,能夠以他剛起步時的那種精神來創造遊戲。藉由這個捫心自問的問題,他發現了正迅速成長中的「獨立遊戲」。


所謂的「商業遊戲開發者」與「獨立遊戲開發者」,實際上的不同之處為何?當一間商業公司啟動一項新專案的時候,他們經常會問的問題是:「誰會給我們所需的資源來支付薪資與帳單?」如果這間公司夠幸運的話,他們可以問:「誰會給我們所需的資源來製作我們想做的遊戲?」而當獨立開發者啟動新的專案時,他們經常會自問的問題則是:「如何以現成可用的資源,製作出我所想要的遊戲?」這就是兩者間最大的不同之處。


在 1982 年時,由 Namco 公司所生產製作的《Pac-Man》,只需要 10 萬美金的開發費用;現今,開發一款 PS3 平台的遊戲,平均花費估計約為 1500 萬美金左右。即使經過通貨膨脹的調整之後,遊戲作品的開發費用仍然呈現出相當大的躍升幅度。而當近年的遊戲開發預算,已然呈現出數以倍計的增長局面之後,銷售量與收益仍然很難產生相對的具體變化。為了抵抗如此情勢,有些缺少開發流程經驗的獨立開發者,會盲目而不加思考地貿然前進,但是那些最成功的獨立開發者們,則能夠善用現存的資源大展身手。


開發遊戲這件事,最令人神魂顛倒之處,莫過於創造這些互動世界的能力,僅侷限於我們所能夠想像以及我們的技術能力所及之處;只要擁有想像力與技術力,你就能夠創造出一個充滿歡笑與樂趣的夢想國度。在這篇文章裡,作者提出九項非常實際有效的訣竅,並且引用了相當多成功的遊戲案例交互印證,帶領各位讀者進一步踏入獨立遊戲開發領域的康莊大道。



身為獨立開發者,通常只能夠運用相當有限的起始資源。藉由不向外借貸資源的方式進行開發,優勢在於他們能夠為自己創造出一個免於外在影響與限制的開發環境。身為獨立遊戲開發者所擁有的唯一義務,就是對他們自己以及購買遊戲的玩家們負責。但是該如何在如此險峻艱困的情況下,利用少量的資源,達到最大化的效益?以下是給獨立遊戲開發者們的九大心訣:


第一訣:設計層面的效率


圍繞著設計層面的考量,往往能夠為你節省最多的資源。為了達到有效率的遊戲設計,開發者可以著重於下列幾個面向:



  • 多人遊玩模式:如果只是製作單人模式與大量單線式的遊戲性內容,並不是一項很有效率的設計。一款設計良好的多人線上遊戲,只需要提供玩家們一組遊戲規則,就能夠以很少的遊戲內容開發時間,創造出近乎無限數量的遊玩時間,同時也能夠大幅增加遊戲的生命週期。

  • 使用者自製內容:如果你沒有資源去製作大量的遊戲內容,那麼為何不給玩家工具讓他們去做?如果提供給玩家合適的工具,幾乎可以斷定,玩家會製作出你八百輩子都沒有想過的東西。《Line Rider》是一款由斯洛伐尼亞學生製作的 Flash 遊戲,在遊戲中玩家能夠自由地畫出可供遊戲角色滑行其上的軌道,藉由這項遊戲機制,玩家們能夠自由發揮創意,網路上也出現許多令人瞠目結舌的遊戲創作影片。只要給玩家們發揮創意的機會,就有可能開啟一場玩家社群中的「創意核武競爭」。

  • 程序化內容:只要利用得當,使玩家能夠由既有設計創造出無限內容的生產系統,將會帶來指數性的設計效益。除了作者所提到的 《DEFCON》以外,《Spore》更是一個將程序化內容發揮得淋漓盡致的絕佳典範。藉由強大的生物創造器與載具創造器,玩家們不僅能夠展示自己獨一無二的創造能力,更能夠非常便利地以 PNG 圖片的格式,將創作物寄送分享給其他的朋友。

  • 避免寫實取向的美術風格:任何遊戲只要採用了寫實取向 (photorealistic) 的美術風格,立即就會落入與《Crysis》等遊戲競爭視覺效果的局面。而這個牌桌上的最低籌碼門檻,每年都在不斷地向上猛漲。隨著每個世代的硬體花費增加,美術素材的製作費用也越來越昂貴,如果繼續發展下去,到最後可能只有那些最大的遊戲開發商們,有能力負擔寫實取向遊戲的開發預算。

  • 重新探索已探索過的技術:最後,還有一項以較少資源取得有效成果的方法,就是去探索那些 15 至 20 年前的主題與設計。《Fez》就是一個相當出色的例子。第一眼看到這個遊戲,會覺得它不過是個尋常的 2D 捲軸式動作遊戲而已,但是當玩家將場景轉向 90 度之後,立即就會發現整個遊戲的場景,實際上是以 3D 的方式巧妙建構而成!(推薦各位觀賞這段展示影片,《Fez》的設計概念確實相當新奇有趣!令我想起《紙片瑪莉歐》與《無限迴廊》的混合體。目前遊戲似乎仍處於開發階段。)


第二訣:利用既存免費、便宜或者開源的技術


攀上前人的背部並不可恥,特別是當你正試圖達到「以少得多」的目的之時。例如全球知名的 FPS 遊戲《Counter Strike》,就是起源於《Half Life》的修改模組。作者 Minh Le 與 Jess Cliffe 藉著對《Half Life》引擎做出基本修改的方式,改變遊戲規則、武器行為以及創建出全新的美術素材,最終製作出震撼整個遊戲界的偉大成果。如果扣除掉時間成本,製作《Counter Strike》的預算近乎於零。


所以如何找到合適並且利用免費的技術來源,對於獨立開發者來說,絕對是必須認真仔細研究的課題。以下列出一些比較著名的免費技術或便宜技術:



  • 繪圖:Ogre、Irrlicht、HGE、Panda3d、SDL。

  • 遊戲引擎:Shockwave、NeoAxis、Torque、Unity、XNA、Game Maker。

  • 物理:ODE、Bullet、PhysX、Havok。

  • 網路:RakNet、SmartFoxServer、HawkNL。

  • 腳本語言:Lua、Python、GameMonkey、Ruby。

  • 音源:OpenAL、SDL_mixer、irrKlang、Audiere、BASS。

  • 使用者介面:AntTweakBar、Guichan、Navi、LibUFO。


更詳盡的遊戲開發函式庫及工具清單列表,請參照「Free Game Development Libraries」


第三訣:數位式發佈


能夠讓遊戲開發者直接將遊戲遞送到玩家們的手上,以效率來說是一項非常巨大的進展。相較於推廣、包裝以及運送實體遊戲到全世界的商店中,藉由網際網路散佈遊戲所需的資源可說是微不足道。《Red Orchestra》是一個以二次世界大戰為主題的 Unreal 引擎修改模組遊戲,在他們獲得競賽首獎並贏得 1 百萬美元的獎金之後,同時也獲得了 Unreal 引擎的正式授權。為了銷售這款遊戲,他們與數間遊戲發行商進行溝通協商,但沒有人對他們的遊戲感興趣。最終與 Valve 接觸後,使這款遊戲登上了 Steam 平台,能夠不受限於發行商與實體通路的限制,在全世界進行販售。


對於開發者或者創作者來說,網際網路的盛行無疑是件值得稱頌盛讚的事情。數位式發佈,就意味著有更多將遊戲送達玩家手上的方法,也代表遊戲作品能夠經過更少的中介者,遊戲開發者不僅能夠得到更高的報酬、保有作品的智財權,也得以直接取得使用者的相關資訊。


第四訣:在開放式平台上開發


如果去詢問任何一位專業開發者,他們都可以告訴你,將遊戲弄上一個封閉式平台需要非常多的資源。新的數位化平台例如 Xbox Live Arcade、WiiWare 或者 PlayStation Network,對於可用資源較少的遊戲來說,能夠視為比較有利的銷售管道。然而 Garage Games 公司的創辦人之一 Jeff Tunnell,也是《Marble Blast Ultra》這款 XBLA 遊戲的開發者,對於 XBLA 平台遊戲的開發預算提出這樣的意見:「目前為止,我不會考慮嘗試以 10 萬美金的預算,去製作一款 XBLA 的遊戲。光是開發套件以及品質認證程序的費用,就會吃掉一半的預算,而只留下很少的資源能提供給實際的遊戲開發程序運用。」(註:上述發言的時間為 2006 年,與現況是否相符仍有待商榷。)


而除了在封閉式平台上競爭所需的高漲花費以外,另外存在的議題,是必須要將你的遊戲弄上平台開發商的遊戲目錄上。Jeff 則將這種情形,比喻成像是「威利旺卡的黃金票卷」般難以取得。如果藉由像是電腦或網路的開放式平台開發遊戲,就能夠將原來必須拿來滿足封閉式平台需求的資源節省下來,投注更多的資源在遊戲本身的開發過程上。


第五訣:共同合作


以少量資源進行遊戲開發的關鍵要點,就在於與其他擁有你所需資源的人共同合作。包括美術設計者、音樂創作者以及程式設計者在內,獨立開發者會尋找具有相近焦點、願意相互合作,或者是願意進行商業交易的其他開發者。另外,多數的獨立遊戲開發者,與那些被商業競爭規範所限制的開發者不同,總是會積極地參與線上社群的活動,並且熱心分享在開發流程中最直接深入的細節。


像是 TIGSource 以及 Indiegamer 這樣的網站,都是獨立遊戲開發者進行討論以及尋求合作的溫床。另一方面來說,共同合作的渴望雖然能夠帶來效益,卻也可能會造成傷害;換句話說,比起現實可靠的支票來說,所謂的熱情與熱忱往往顯得反覆無常而多變。最成功的合作模式,往往集中於那些能夠共同分享目標與動機的開發者們身上。


第六訣:考慮非傳統的賺錢方法


除了傳統的商業模式以外,應該還存在著其他更具有效率的賺錢方法。即使是一個被設計為免費遊玩的遊戲,仍然能夠以各種不同的方法賺取金錢;廣告、微型付款、販售虛擬道具,或甚至在遊戲的製作過程中就開始販賣遊戲。


其中一個實例是由 Gene Endrody 所營運的 MaidMarian 網站。他使用 Shockwave 軟體,製作出讓玩家能夠在網頁上遊玩的 3D 多人線上遊戲,不僅不向玩家收取遊戲費用,同時也沒有對遊戲內容做出任何限制。藉由這樣的運作方式,MaidMarian 每個月可以吸引多達 150 萬的獨立造訪人次,並且達到 4000 人的同時上線人數,僅仰賴著網頁中的廣告,就能替他帶來相當可觀的收益。當 Gene 離開他所擔任的技術美術設計主管職務,全職專注於製作網頁遊戲時,他所得到的收益已經比之前的工作所得更多。


第七訣:重新定義成功


一個只使用超級大作 (blockbuster) 部分資源開發的遊戲,同樣也不需要賣到與超級大作相同的銷售成績,才能夠獲得財務面的成功。多數的獨立開發者,並不會花費資源試著去開發那些迎合大眾市場的功能特點,他們通常會將資源花費在他們認為重要之處,同時也是目標客群認為重要之處。


《Gish》這個遊戲為例,遊戲開發公司 Chronic Logic 曾公開分享他們的銷售成績。根據數據顯示,他們總計獲得了約 121,000 美元的款項收入。這筆數目對許多大型遊戲開發商來說,或許只是微不足道的零頭而已,但是對於一個只有 3 位開發者的團隊,6 個月的製作時間,約 5,700 美元開發預算的遊戲來說,這筆數目已經取得足夠的成功,並且讓他們得以繼續進行遊戲續作的開發了!


然而,上述的例子並不表示獨立遊戲開發者無法賺大錢。《RuneScape》是一個由 Andrew Gower 與 Paul Gower 兩兄弟,以 Java 為基礎開發的多人線上角色扮演遊戲。在提供給玩家基礎版本的免費遊戲之後,他們也實作出了一個可選擇的會員服務機制,提供給付費玩家更多額外的區域、任務以及道具。而對於沒有付費的玩家,則會在遊戲的視窗上頭顯示廣告連結。


任何一位開發者見到《RuneScape》,都能夠看出這個遊戲的製作資源非常稀少,既沒有閃閃動人的圖像,也缺少特別錄製的音軌;當然,遊戲本身的遊戲性,必定要能夠對到某些市場區隔裡的玩家胃口。而時至今天,Andrew Gower 已經是英國排名第 654 位富有的人。


第八訣:使用替代的資金來源


只要設法有效降低開發遊戲所需的資源數,能夠找到不同資金管道挹注的機會,也會呈現出指數性的提升。


《Mount & Blade》是一款中世紀的戰爭模擬管理角色扮演遊戲,由土耳其的一對夫妻所開發製作。遊戲本身一直持續在開發新功能的狀態中,新玩家能夠下載免費的試玩版,而完整版遊戲的價格,則會隨著遊戲增加的功能項目而逐漸向上增加。最早的付費玩家,僅需付出 6 美元的價格購買遊戲,而現在的標價則已提升至 30 美元。如此新穎的商業模式,不僅為他們提供了良好的資金來源,也給予了早期購買遊戲的忠實玩家們更多的優惠及利益,無形中強化了遊戲社群的忠誠度。


第九訣:個人化


如果你所開發的東西正好擊中玩家的心弦,那麼圍繞著你的遊戲的玩家社群,將能夠成為一項非常具有威力而且免費(或便宜)的資源。讓他們行銷你的遊戲,任命他們擔任論壇的板主,准許他們創造遊戲的內容吧!


《Kingdom of Loathing》是一個簡單到不可思議程度的網頁式多人線上遊戲。在這個具諷刺風格的冒險遊戲中,玩家所扮演的英雄既沒有精美細膩的 3D 模型,也不是俏皮可愛的 2D 風格,而僅僅是一個如火柴棒般的人物形象!儘管如此,遊戲目前的註冊玩家人數,已經超越了 140 萬人!除了玩家之間的口耳相傳之外,遊戲開發者並沒有做其他太多的行銷與宣傳。這款遊戲所獨具的個人特質,已經證明了自己的成功。


根據 Wired 網站的一篇報導指出,有相當數量的玩家選擇用捐贈 (donate) 的方式,付出少量金錢給遊戲的創造者 Zack Johnson。光是捐贈的收入,就能夠讓他辭去原本的日常工作,並且還雇用了六位成員以協助處理遊戲的臭蟲問題、伺服器及經濟體系。甚至在 eBay 網站上,還有人願意以每 1 美金購買 480,000 枚遊戲中的貨幣。


以上,就是每位獨立遊戲開發者都應當牢記於心的獨孤九訣。



By taking a look at Independent games, developers might find some inspiration for solutions to the challenges they face.


上述九項秘訣,或許不是什麼全新的創見,但是非常有助於導正目前許多關於獨立遊戲開發領域的誤解。很多遊戲開發者,可能會認為在商業遊戲與獨立遊戲之間,有一道巨大而不可跨越的鴻溝存在。其中一邊,是擁有巨額預算的超級遊戲大作;另一邊,則只是些如同 Match-3 消除類型的小遊戲。而在這兩者中間的巨大斷層面,沒有任何東西能夠生存。


所幸事實並非如此。每一年,我們都能夠看到獨立遊戲開發者所展現的創意活力與遊戲作品,當科技更加進步並且能夠更容易地接近全球玩家社群時,遊戲開發者也會被賦予更多在斷層面生存的機會與空間。正如作者所述,所有的專業遊戲開發者們,都應該清楚認識持續成長中的獨立遊戲社群。看一看這些風格各異、匠心獨具的獨立遊戲,或許你也能夠找到克服眼前挑戰的靈感與解決方案。


如果你和我一樣,已經在遊戲業界裡翻滾了四、五年以上或者更久的時間,應該多少都會對於文章最前頭的一席話有些感觸吧。套句星爺電影中的台詞,身為遊戲開發者,你是否有時候也會「覺得孤單寂寞覺得冷」?是否在現實與理想的拉扯掙扎中,已經埋葬了當初剛踏入遊戲公司的那份渴望?是否在每日沈重反覆的工作中,已經忘卻了從前立志時的初心?但是,誰說遊戲人只能有一種出路?從這篇文章裡,我們已經在許多成功的實例中,見識到「遊戲開發」的各種不同的途徑以及發展的可能性。


最後要提醒各位的是,即使獨立遊戲開發領域看起來是如此地美妙動人而且充滿機會,但是同樣別忘了在開發者背後,所需承擔的風險壓力與所需具備的超卓才能。就我所知,大多數成功的獨立遊戲開發者,都是原先在遊戲業界裡具有一流專業能力與深厚工作經驗的 Pro 級達人。所以如果你抱持著遠大的理想,期望自己能夠製作出跨越全球市場的獨立遊戲,那麼請先在遊戲公司裡,好好的訓練、磨練、鍛鍊自己吧!