ドット絵ならアニメーションでしょ。
成果物
出力結果は以下。
type | result |
---|---|
gif | |
png | |
webp |
コード
コード(520行)
main.py
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys, os, numpy, PIL from PySide2 import QtCore, QtGui, QtWidgets from PIL import Image, ImagePalette class Window(QtWidgets.QMainWindow): def __init__(self): super(self.__class__, self).__init__() self.setAcceptDrops(True) self.widget = Widget(self) self.setCentralWidget(self.widget) menu_file = QtWidgets.QMenu('File', self) menu_file.addAction(self.widget.GraphicsView.Scene.Drawable.SaveAction) self.menuBar().addMenu(menu_file) menu_frame = QtWidgets.QMenu('Animation', self) menu_frame.addAction(FrameListView.AddFrameAction) menu_frame.addAction(FrameListView.DeleteFrameAction) self.menuBar().addMenu(menu_frame) self.show() # Frame側でも使いたいので globals()['Window'] = self def mousePressEvent(self, event): super(self.__class__, self).mousePressEvent(event) self.widget.update() def mouseMoveEvent(self, event): super(self.__class__, self).mouseMoveEvent(event) self.widget.update() def dragEnterEvent(self, event): super(self.__class__, self).dragEnterEvent(event) self.widget.update() def dragMoveEvent(self, event): super(self.__class__, self).dragMoveEvent(event) def dropEvent(self, event): super(self.__class__, self).dropEvent(event) class Widget(QtWidgets.QWidget): def __init__(self, parent): super(self.__class__, self).__init__(parent) self.setAcceptDrops(True) self.view = GraphicView() self.framelist = FrameListView() # DrawableItemで使いたいので globals()['FrameListView'] = self.framelist scroller1 = QtWidgets.QScrollArea() scroller1.setWidget(self.view) scroller2 = QtWidgets.QScrollArea() scroller2.setWidget(self.framelist) layout = QtWidgets.QGridLayout() layout.addWidget(scroller1, 0, 0) layout.addWidget(scroller2, 0, 1) self.setLayout(layout) self.resize(self.view.width(), self.view.height()) self.setWindowTitle("QAction") self.show() @property def GraphicsView(self): return self.view def mousePressEvent(self, event): super(self.__class__, self).mousePressEvent(event) self.view.scene().update() self.view.update() def mouseMoveEvent(self, event): super(self.__class__, self).mouseMoveEvent(event) self.view.scene().update() self.view.update() def dragEnterEvent(self, event): super(self.__class__, self).dragEnterEvent(event) self.view.dragEnterEvent(event) self.view.scene().update() self.view.update() def dragMoveEvent(self, event): super(self.__class__, self).dragMoveEvent(event) def dropEvent(self, event): super(self.__class__, self).dropEvent(event) class GraphicView(QtWidgets.QGraphicsView): def __init__(self): QtWidgets.QGraphicsView.__init__(self) self.setAcceptDrops(True) self.setWindowTitle("QGraphicsScene draw Grid") self.__editorScene = EditorScene(self) self.setScene(self.__editorScene) def mousePressEvent(self, event): super(self.__class__, self).mousePressEvent(event) self.scene().update() def mouseMoveEvent(self, event): super(self.__class__, self).mouseMoveEvent(event) self.scene().update() @property def Scene(self): return self.__editorScene def dragEnterEvent(self, event): super(self.__class__, self).dragEnterEvent(event) self.scene().update() def dragEnterEvent(self, event): super(self.__class__, self).dragMoveEvent(event) self.scene().update() def dropEvent(self, event): super(self.__class__, self).dropEvent(event) self.scene().update() class EditorScene(QtWidgets.QGraphicsScene): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.size = 16 self.scale = 32 self.setSceneRect(0, 0, self.size*self.scale, self.size*self.scale) self.grid = GridItem() self.addItem(self.grid) self.background = BackgroundItem() self.addItem(self.background) self.drawable = DrawableItem() self.addItem(self.drawable) self.background.setZValue(0) self.drawable.setZValue(1) self.grid.setZValue(9999) # Frame側でも使いたいので globals()['Drawable'] = self.drawable def mousePressEvent(self, event): for item in self.items(): item.mousePressEvent(event) super(self.__class__, self).mousePressEvent(event) def mouseMoveEvent(self, event): for item in self.items(): item.setAcceptHoverEvents(True) item.mouseMoveEvent(event) super(self.__class__, self).mouseMoveEvent(event) def dragEnterEvent(self, event): for item in self.items(): item.setAcceptDrops(True) if event is type(QtWidgets.QGraphicsSceneDragDropEvent): item.dragEnterEvent(event) if event is type(QtWidgets.QGraphicsSceneDragDropEvent): super(self.__class__, self).dragEnterEvent(event) def dragMoveEvent(self, event): for item in self.items(): item.setAcceptDrops(True) if event is type(QtWidgets.QGraphicsSceneDragDropEvent): item.dragEnterEvent(event) if event is type(QtWidgets.QGraphicsSceneDragDropEvent): super(self.__class__, self).dragEnterEvent(event) def dropEvent(self, event): for item in self.items(): item.setAcceptDrops(True) item.dropEvent(event) if event is type(QtWidgets.QGraphicsSceneDragDropEvent): super(self.__class__, self).dropEvent(event) @property def Grid(self): return self.grid @property def Background(self): return self.background @property def Drawable(self): return self.drawable class DrawableItem(QtWidgets.QGraphicsRectItem): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.setAcceptDrops(True) self.setAcceptHoverEvents(True) self.scale = 32 self.pixels = Pixels() self.actions = {} self.__create_save_action() def __create_save_action(self): a = QtWidgets.QAction('Save') a.setObjectName('Save') a.setShortcut('Ctrl+S') a.triggered.connect(self.Pixels.save) self.actions['Save'] = a def paint(self, painter, option, widget): painter.fillRect(widget.rect(), QtGui.QBrush( QtGui.QColor(0,0,0,0), QtCore.Qt.SolidPattern)) for y in range(self.pixels.Height): for x in range(self.pixels.Width): if 1 == self.pixels.Pixels[y][x]: painter.fillRect(x*self.scale, y*self.scale, self.scale, self.scale, QtGui.QBrush( QtGui.QColor(255,0,0,128), QtCore.Qt.SolidPattern)) def mouseMoveEvent(self, event): pos = event.scenePos() x = int(pos.x()//self.scale) y = int(pos.y()//self.scale) if event.buttons() & QtCore.Qt.LeftButton: self.pixels.Pixels[y][x] = 1 # print(x, y, self.pixels.Pixels) for idx in FrameListView.selectedIndexes(): FrameListModel.Frames[idx.row()].Pixels.Pixels[y][x] = 1 FrameListModel.update_icon(idx) # print(idx.row(), 'Drawable', x, y, FrameListModel.Frames[idx.row()].Pixels.Pixels) # FrameListView.mouseMoveEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) # FrameListView.mouseMoveEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) # FrameListView.mouseMoveEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) # FrameListView.update_icon() # FrameListView.update() # FrameListView.repaint() # 再描画。意味不明だがListViewのマウスイベントを発行すればListViewが再描画されることを発見した。なぜかupdate()やrepaint()では一切再描画されない。 # だがListViewの先頭項目が選択されてしまう。このせいでバグるため再描画させられない。 # super(FrameListView.__class__, FrameListView).mousePressEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) # FrameListView.mousePressEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) """ FrameListView.update() FrameListView.updateGeometry() FrameListView.repaint() Window.update() Window.repaint() """ FrameListView.update() Window.update() if event.buttons() & QtCore.Qt.RightButton: self.pixels.Pixels[y][x] = 0 for idx in FrameListView.selectedIndexes(): FrameListModel.Frames[idx.row()].Pixels.Pixels[y][x] = 0 FrameListModel.update_icon(idx) print(idx.row(), 'Drawable', x, y, FrameListModel.Frames[idx.row()].Pixels.Pixels) # super(FrameListView.__class__, FrameListView).mousePressEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) # FrameListView.mousePressEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) """ FrameListView.update() FrameListView.updateGeometry() FrameListView.repaint() Window.update() Window.repaint() """ def mousePressEvent(self, event): pos = event.scenePos() x = int(pos.x()//self.scale) y = int(pos.y()//self.scale) if event.buttons() & QtCore.Qt.LeftButton: self.pixels.Pixels[y][x] = 1 for idx in FrameListView.selectedIndexes(): FrameListModel.Frames[idx.row()].Pixels.Pixels[y][x] = 1 FrameListModel.update_icon(idx) print(idx.row(), 'Drawable', x, y, FrameListModel.Frames[idx.row()].Pixels.Pixels) # super(FrameListView.__class__, FrameListView).mousePressEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) # FrameListView.mousePressEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) """ FrameListView.update() FrameListView.updateGeometry() FrameListView.repaint() Window.update() Window.repaint() """ if event.buttons() & QtCore.Qt.RightButton: self.pixels.Pixels[y][x] = 0 for idx in FrameListView.selectedIndexes(): FrameListModel.Frames[idx.row()].Pixels.Pixels[y][x] = 0 FrameListModel.update_icon(idx) print(idx.row(), 'Drawable', x, y, FrameListModel.Frames[idx.row()].Pixels.Pixels) # super(FrameListView.__class__, FrameListView).mousePressEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) # FrameListView.mousePressEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier)) """ FrameListView.update() FrameListView.updateGeometry() FrameListView.repaint() Window.update() Window.repaint() Window.widget.update() Window.widget.repaint() """ def mouseReleaseEvent(self, event): pass def mouseDoubleClickEvent(self, event): pass @property def Pixels(self): return self.pixels @Pixels.setter def Pixels(self, value): #self.pixels = value for y in range(value.Height): for x in range(value.Width): self.pixels.Pixels[y][x] = value.Pixels[y][x] @property def SaveAction(self): return self.actions['Save'] def dragEnterEvent(self, event): if event.mimeData().hasUrls(): event.acceptProposedAction() def dropEvent(self, event): for url in event.mimeData().urls(): file_name = url.toLocalFile() print("Dropped file: " + file_name) self.Pixels.load(file_name) class BackgroundItem(QtWidgets.QGraphicsRectItem): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.size = 16 self.scale = 32 self.colors = [QtGui.QColor(196,196,196,255), QtGui.QColor(232,232,232,255)] def paint(self, painter, option, widget): for i in range(self.size*self.size): x = (i % self.size) y = (i // self.size) color = QtGui.QColor(128,128,128,255) if 0 == (i % 2) and 0 == (x % 2) else QtGui.QColor(196,196,196,255) painter.fillRect(x * (self.scale), y * (self.scale), self.scale//2, self.scale//2, self.colors[0]) painter.fillRect(x * (self.scale)+self.scale//2, y * (self.scale)+self.scale//2, self.scale//2, self.scale//2, self.colors[0]) painter.fillRect(x * (self.scale)+self.scale//2, y * (self.scale), self.scale//2, self.scale//2, self.colors[1]) painter.fillRect(x * (self.scale), y * (self.scale)+self.scale//2, self.scale//2, self.scale//2, self.colors[1]) class GridItem(QtWidgets.QGraphicsRectItem): def __init__(self, *args, **kwargs): super(self.__class__, self).__init__(*args, **kwargs) self.size = 16 self.scale = 32 def paint(self, painter, option, widget): painter.fillRect(widget.rect(), QtGui.QBrush(QtGui.QColor(0,0,0,0), QtCore.Qt.SolidPattern)) lines = [] for y in range(self.size+1): lines.append(QtCore.QLine(0, y*self.scale, self.size*self.scale, y*self.scale)) for x in range(self.size+1): lines.append(QtCore.QLine(x*self.scale, 0, x*self.scale, self.size*self.scale)) painter.drawLines(lines) class Pixels: def __init__(self): self.width = 16 self.height = 16 self.pixels = numpy.zeros(self.width*self.height, dtype=int).reshape(self.height, self.width) @property def Pixels(self): return self.pixels @property def Width(self): return self.width @property def Height(self): return self.height def save(self): print(os.getcwd()) self.save_txt() for ext in ('gif', 'png', 'webp'): self.save_raster(ext) for ext in ('gif', 'png', 'webp'): self.save_animation(ext) def load(self, file_path): ext = os.path.splitext(file_path)[1].lower()[1:] if '' == ext: raise Exception('拡張子が必要です。png,gif,webp,txt形式のいずれかに対応しています。') elif 'txt' == ext: self.load_txt(file_path) elif 'gif' == ext: self.load_gif(file_path) elif 'png' == ext: self.load_png(file_path) elif 'webp' == ext: self.load_webp(file_path) else: raise Exception('拡張子が未対応です。png,gif,webp,txt形式のいずれかに対応しています。') def save_txt(self): with open(os.path.join(os.getcwd(), 'pixels.txt'), 'w') as f: f.write('\n'.join([''.join(map(str, self.pixels[y].tolist())) for y in range(self.height)])) def load_txt(self, file_path): with open(file_path, 'r') as f: lines = f.read().split('\n') self.height = len(lines) self.width = len(lines[0]) self.pixels = numpy.zeros(self.width*self.height, dtype=int).reshape(self.height, self.width) x = 0; y = 0; for line in lines: for c in line: self.pixels[y][x] = int(c, 16) x += 1 y += 1 x = 0 def save_raster(self, ext): image = Image.new('1', (self.width, self.height)) image.putdata(self.pixels.reshape(self.width * self.height).tolist()) print(ext) image.save(os.path.join(os.getcwd(), 'pixels.' + ext), optimize=True, lossless=True) def save_animation(self, ext): print(ext) if len(FrameListView.Model.Frames) < 2: return images = [] for frame in FrameListView.Model.Frames: image = Image.new('P', (frame.Pixels.Width, frame.Pixels.Height)) image.putpalette([0,0,0,255,255,255]) image.putdata(frame.Pixels.Pixels.reshape(frame.Pixels.Width * frame.Pixels.Height).tolist()) images.append(image) image.save(os.path.join(os.getcwd(), 'animation.' + ext), save_all=True, append_images=images, duration=100, loop=0, optimize=False) # image.save(os.path.join(os.getcwd(), 'pixels.' + ext), optimize=True, lossless=True, save_all=True, append_images=images) def load_png(self, file_path): image = Image.open(file_path, mode='r') image = image.convert('1') print(len(image.getdata()), list(image.getdata())) self.pixels = numpy.array(list(map(lambda x: 0 if 0 == x else 1, list(image.getdata())))).reshape(image.size[1], image.size[0]) self.width, self.height = image.size print(self.width, self.height) def load_gif(self, file_path): # 値が0/255で出力されてしまうので0/1に変換する image = Image.open(file_path, mode='r') self.width, self.height = image.size self.pixels = numpy.array(list(map(lambda x: 0 if 0 == x else 1, list(image.getdata())))).reshape(self.height, self.width) def load_webp(self, file_path): # 値が[0,0,0]/[255,255,255]で出力されてしまうので0/1に変換する image = Image.open(file_path, mode='r') self.width, self.height = image.size self.pixels = numpy.array(list(map(lambda x: 0 if (0,0,0) == x else 1, list(image.getdata())))).reshape(self.height, self.width) class FrameListView(QtWidgets.QListView): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.resizeContents(16, 16*16) self.model = FrameListModel() self.model.appendRow() self.setModel(self.model) globals()['FrameListModel'] = self.model self.resize(64, 16*32) self.actions = {} self.__create_add_frame_action() self.__create_delete_frame_action() self.setCurrentIndex(self.model.index(0,0)) self.show() def mouseMoveEvent(self, event): super(self.__class__, self).mousePressEvent(event) def mousePressEvent(self, event): super(self.__class__, self).mousePressEvent(event) for idx in self.selectedIndexes(): frame = idx.data(QtCore.Qt.UserRole) Drawable.Pixels = frame.Pixels Window.widget.view.scene().update() def update_icon(self): for idx in self.selectedIndexes(): self.model.update_icon(idx) self.update() self.repaint() @property def Model(self): return self.model @property def AddFrameAction(self): return self.actions['AddFrame'] @property def DeleteFrameAction(self): return self.actions['DeleteFrame'] def __create_add_frame_action(self): a = QtWidgets.QAction('Add frame') a.setObjectName('AddFrame') a.setShortcut('Ctrl+Alt+N') a.triggered.connect(self.model.appendRow) self.actions['AddFrame'] = a def __create_delete_frame_action(self): a = QtWidgets.QAction('Delete frame') a.setObjectName('DeleteFrame') a.setShortcut('Ctrl+Alt+D') a.triggered.connect(self.__delete_frame) self.actions['DeleteFrame'] = a def __delete_frame(self): if len(self.model.Frames) < 2: return for idx in self.selectedIndexes(): if idx.row() == self.model.rowCount()-1: self.setCurrentIndex(self.model.index(idx.row()-1,0)) else: self.setCurrentIndex(self.model.index(idx.row(),0)) self.model.removeRow(idx) for idx in self.selectedIndexes(): frame = idx.data(QtCore.Qt.UserRole) Drawable.Pixels = frame.Pixels Window.widget.view.scene().update() # self.update() class FrameListModel(QtCore.QAbstractListModel): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.frames = [] def rowCount(self, parent=QtCore.QModelIndex()): if parent.isValid(): return 0 return len(self.frames) def data(self, index, role=QtCore.Qt.DisplayRole): if role == QtCore.Qt.DecorationRole: return self.frames[index.row()].Icon elif role == QtCore.Qt.UserRole: return self.frames[index.row()] def appendRow(self): self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount()) self.frames.append(Frame()) self.endInsertRows() def removeRow(self, index): self.beginRemoveRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount()) print(index.row()) self.frames.pop(index.row()) self.endRemoveRows() def update_icon(self, index): self.frames[index.row()].update_icon() @property def Frames(self): return self.frames class Frame: def __init__(self): self.pixels = Pixels() self.icon = QtGui.QImage(self.pixels.Width, self.pixels.Height, QtGui.QImage.Format_Mono) self.update_icon() def update_icon(self): image = QtGui.QImage(self.pixels.Width, self.pixels.Height, QtGui.QImage.Format_Mono) for y in range(self.pixels.Height): for x in range(self.pixels.Width): image.setPixel(x, y, self.pixels.Pixels[y][x]) if 1 < self.pixels.Pixels[y][x]: print(self.pixels.Pixels[y][x]) self.icon = QtGui.QIcon(QtGui.QPixmap.fromImage(image)) @property def Pixels(self): return self.pixels @Pixels.setter def Pixels(self, value): self.pixels = value @property def Icon(self): return self.icon @Icon.setter def Icon(self, value): self.icon = value if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = Window() sys.exit(app.exec_())
python3 main.py
苦労した所
ListViewの項目削除
QAbstractItemModelのbeginRemoveRows()
とendRemoveRows()
を使う。この作法を知らないと実装できない。
class FrameListModel(QtCore.QAbstractListModel): ... def removeRow(self, index): self.beginRemoveRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount()) self.frames.pop(index.row()) self.endRemoveRows()
ListViewの項目選択
最初に1番目の項目を選択した状態にしたかった。ListView.setCurrentIndex()
を使う。
self.setCurrentIndex(self.model.index(0,0))
項目を削除したとき、選択状態が解除され、何も選択されない状態になってしまった。これを解決するため、現在の選択位置ごとに応じて次にセットする位置を分岐してやる必要があった。
また、先に削除すると何も選択されない状態になってしまう。よって先に項目選択してから削除した。
if idx.row() == self.model.rowCount()-1: self.setCurrentIndex(self.model.index(idx.row()-1,0)) else: self.setCurrentIndex(self.model.index(idx.row(),0)) self.model.removeRow(idx)
アニメ出力
Pillowのバージョンが5と古かったため、APNG出力でエラーになった。以下で更新して7.1.2にすると成功した。
pip3 install -U pillow
Pillow
のsave()
を使う。save_all
, append_images
引数を渡す。
ただ、mode
が1
では真っ黒な画像しか出力されなかった。仕方なくP
モードにして、2色のパレットを作成することで対応した。
def save_animation(self, ext): if len(FrameListView.Model.Frames) < 2: return images = [] for frame in FrameListView.Model.Frames: image = Image.new('P', (frame.Pixels.Width, frame.Pixels.Height)) image.putpalette([0,0,0,255,255,255]) image.putdata(frame.Pixels.Pixels.reshape(frame.Pixels.Width * frame.Pixels.Height).tolist()) images.append(image) image.save(os.path.join(os.getcwd(), 'animation.' + ext), save_all=True, append_images=images, duration=100, loop=0, optimize=False)
バグ
- ドット描画時にドット抜けする
- ドット描画時にリストのアイコンが再描画されない
1. ドット描画時にドット抜けする
マウスを高速にドラッグすると、ドット抜けしてしまう。
ドット描画アルゴリズムを変更すれば解決できるかもしれない。つまりmouseMoveEvent
発火時に現在座標を保存しておき、マウスボタンを離してドロップしたとき、座標間を線で結ぶようにする。面倒そうなので後回しにしている。
2. ドット描画時にリストのアイコンが再描画されない
項目 | 値 |
---|---|
期待 | ドット描画時にリストのアイコンを再描画したい |
実際 | リストをクリックすると再描画される |
頑張ったが、できなかった。
update()
,repaint()
しても再描画されなかった- なぜか
mouseMoveEvent()
を発火すると再描画された- リスト項目の先頭が選択されてしまう別のバグが生じるため不採用
FrameListView.mouseMoveEvent(QtGui.QMouseEvent(QtCore.QEvent.MouseMove, event.pos(), event.button(), event.buttons(), QtCore.Qt.NoModifier))
対処不能のため、放置せざるを得ない。リストをクリックすれば再描画される。それで我慢する。
所感
他にも以下をしたかった。が、別のUIが必要になるため、今回はやめた。
- アニメ
- 入力
- 間隔
- ループ回数
- 表示
- オニオン
- 入力
以下の機能くらいは欲しい。
- キャンバスサイズの変更
- パレット(最大16色)
他にも山ほど要件がある。
要件
- 色深度(1/2/4/8bit color)
- アルファチャンネル付き
- 指定インデックスを透明色にする
- レイヤ
- 各種描画ツール
- 左右反転・上下反転
- パレット編集(index入替)
- 色見本、配色見本
- パレット提案、配色パターン提案
- キーコンフィグ
- マウスジェスチャ
- 手書き入力
- コマンド入力操作
- 音声入力
- プログラム入力(マクロ入力)
- プロセス間通信(自作ミニツールとの連動(アドオン))
- アドオンSDK
- マップエディタ
- ゲームエンジン(物理演算、衝突判定、キー応答など)
- 3D連携(ボクセル作成、3Dテクスチャ)
- リプレイ動画用データ(テキストエディタで編集し解説文を簡単に表示できるとか)
- サイトジェネレータ
- 外部サービス連携
- IPFSでサイト構築する
雰囲気から色見本や配色パターンを提案して欲しい。
マウスやショートカットキーだけでなく、vimのようにコマンド入力でも操作したい。たとえばdrawrect 0 0 10 10
のように。
リプレイ動画をテキストファイルで保存したい。それを読込すればそのまま再生される。これを見れば誰かがドット絵を作った過程をそのまま再現できる。ついでに解説文の表示などもできればなおよし。
ドット絵ファーストでゲーム作りができたら楽しそう。そこまでできなくとも、ドット絵を動かせるエディタが欲しい。キー入力に応じてアニメーションするような。方向キーで歩行グラ、Aキーで攻撃アニメ、Bキーでジャンプとか。アニメと連動する効果音も作成できたらなおよし。
3Dとも連携させたい。3Dドット絵(ボクセル)を作るとか、3Dポリゴンにテクスチャとしてドット絵を貼って表示するとか。それを動画にしてgif,apng,webp,mp4などに出力するとか。
サイト作成も自動化したい。GitHubPages等のホストを使って。ドット絵やイラスト集、講座系、ミニゲーム。それらをドット絵を書いただけで作ってくれるような。
TweetやToot,ピンタレストなど外部サービス連携も実装したい。サッと描いてすぐ投稿できるような。あるいは1日寝かせて予約投稿するとか。中央集権ネットワークから脱却し、IPFSを使ってサイト構築できるとなおよし。
要件が多すぎて絶対ムリだろうな。
対象環境
- Raspbierry pi 4 Model B
- Raspbian buster 10.0 2019-09-26 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 4.19.97-v7l+ #1294 SMP Thu Jan 30 13:21:14 GMT 2020 armv7l GNU/Linux