とあるゲームにてバトルが始まるときのフェードアウト表現（黒い線が画面を回りながら暗転する）を簡易実装してみた

## 概要
とあるゲームにてバトルが始まるときのフェードアウト表現（黒い線が画面を回りながら暗転する）をどう実装すればいいのか考えて簡易的に実装してみましたという記事です。

以下のようなものです。
![effect.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344880/426956b3-1df9-40de-4b7b-345a313714b8.gif)

# 実装
ubuntu18.04環境で、python3, pyqt5を使用しました。

### インストール

```
# pyqt5 install
pip3 install pyqt5
```

### 処理

以下の2stepです

```
- step1. コマンド実行時点のスクリーンショットを撮る
- step2. 黒色のブロックをpyqt5で順番に表示
```

### step1. コマンド実行時点のスクリーンショットを撮る

```
# スクリーンショット
gnome-screenshot -f test.png
```

### step2. 黒色のブロックをpyqt5で順番に表示

```python:test.py
# -*- coding:utf-8 -*-
## qt5
from PyQt5.QtWidgets import * 
from PyQt5 import QtCore, QtGui 
from PyQt5.QtGui import * 
from PyQt5.QtCore import * 
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QSizePolicy
from PyQt5.QtGui import QImage, QPalette, QPixmap
# 追加したimport
from PyQt5.QtGui import QPainter, QFont, QColor, QPen
from PyQt5.QtCore import Qt

import sys 
import time
from time import sleep

class Window(QMainWindow): 
  
    def __init__(self): 
        super(QMainWindow, self).__init__()
        self.image = QImage("./test.png")
        # 事前に任意のサイズにリサイズしておくと後処理が楽
        # gnome-screenshot -f test.png などでキャプチャしておく
        
        self.window = QWidget()
        self.window.setWindowTitle('Image View')
        self.window.setFixedSize(self.image.width(), self.image.height())
        self.setFixedSize(self.image.width(), self.image.height())

        self.painter = QPainter()
        self.imageLabel = QLabel()
        self.layout = QVBoxLayout()

        # creating a timer object 
        self.TimerUpdate_mSec = 15
        self.TimerUpdate_cnt = 0
        self.TimerUpdate_cnt_step = 0        
        timer = QTimer(self) 
        timer.timeout.connect(self.callback_draw)
        timer.start(self.TimerUpdate_mSec)

    # timer callback function 
    def callback_draw(self):

        # -----
        # 加工したいイメージを渡して編集開始
        self.painter.begin(self.image)
        # 塗りつぶし範囲指定
        img_width  = self.image.width()
        img_height = self.image.height()
        block_ratio = 13 #10
        x_size = img_width/block_ratio  #100
        y_size = img_height/block_ratio  #100

        ## 0
        current_position=int(self.TimerUpdate_cnt_step%4) #0,1,2,3
        loop_cnt=int(self.TimerUpdate_cnt_step/4)
        #print(str(current_position)+" "+str(loop_cnt))

        if current_position == 0:
            x_pos  = int((self.TimerUpdate_cnt+loop_cnt) * x_size % img_width)
            y_pos  = int(loop_cnt * y_size % img_height)
            #y_pos  = int(self.TimerUpdate_cnt * x_size / img_width) * y_size
            if self.TimerUpdate_cnt >= block_ratio-1-loop_cnt:
                self.TimerUpdate_cnt = 0
                self.TimerUpdate_cnt_step += 1
            self.TimerUpdate_cnt += 1

        ## 1
        elif current_position == 1:
            x_pos  = int((block_ratio-1-loop_cnt) * x_size % img_width)
            y_pos  = int((self.TimerUpdate_cnt+loop_cnt) * y_size % img_height)
            if self.TimerUpdate_cnt >= block_ratio-1-loop_cnt+1:
                self.TimerUpdate_cnt = 0
                self.TimerUpdate_cnt_step += 1
            self.TimerUpdate_cnt += 1

        ## 2
        elif current_position == 2:
            x_pos  = int((block_ratio-1-self.TimerUpdate_cnt-loop_cnt) * x_size % img_width)
            y_pos  = int((block_ratio-1-loop_cnt) * y_size % img_height)
            if (block_ratio-1-self.TimerUpdate_cnt <= loop_cnt):
                self.TimerUpdate_cnt = 0
                self.TimerUpdate_cnt_step += 1
            self.TimerUpdate_cnt += 1

        ## 3
        elif current_position == 3:
            x_pos  = int(loop_cnt * x_size % img_width)
            y_pos  = int((block_ratio-1-self.TimerUpdate_cnt-loop_cnt) * y_size % img_height)
            if (block_ratio-1-self.TimerUpdate_cnt <= loop_cnt+1):
                self.TimerUpdate_cnt = 0
                self.TimerUpdate_cnt_step += 1
            else:
                self.TimerUpdate_cnt += 1

        print(str(x_pos) + " " + str(y_pos))
        self.painter.fillRect(x_pos, y_pos, x_size, y_size, Qt.black)
        self.painter.end()
        # -----

        # ラベルに読み込んだ画像を反映
        self.imageLabel.setPixmap(QPixmap.fromImage(self.image))

        self.layout.addWidget(self.imageLabel)

        self.window.setFixedSize(self.image.width(), self.image.height())
        self.window.resize(self.image.width(), self.image.height())
        self.window.setLayout(self.layout)

        #self.window.show()
        self.window.showFullScreen()

        #self.TimerUpdate_cnt += 1
        if self.TimerUpdate_cnt_step >= 25:
            print("end")
            time.sleep(self.TimerUpdate_mSec*0.001/4)
            sys.exit(0)

if __name__ == '__main__':
    # create pyqt5 app 
    app = QApplication(sys.argv)   
    # create the instance of our Window 
    window = Window() 
    # start the app 
    sys.exit(app.exec()) 
```

以下の通り実行するだけ

```shell:test.sh
gnome-screenshot -f test.png
python3 test.py
```

```
bash test.sh
```

入力画像のサイズによっては黒いブロックがずれることがあるので、
入力画像に応じて、ブロックのサイズや、カウンターの最大値は適宜変更するとよい。

## 感想

簡易的に実装しただけでもっとpyqt5や、グラフィックライブラリを使えるようになりたい。
OpenGL、Qt transparent機能を利用するとより改善されるなど、ご意見があれば頂けると嬉しいです。

追記：
回転が逆回転のような気もしますが、気にしないで下さい。。
https://youtu.be/JlY3585-rKk?t=55

# 参考
[https://doc.qt.io/qt-5/qwidget.html](https://doc.qt.io/qt-5/qwidget.html)
[QImage の読み書きあれこれ](https://graphics.hatenablog.com/entry/2020/03/08/045032)
[Python + PyQt5 画像の上に文字や矩形を上書き(drawText, drawRect, fillRect)](https://symfoware.blog.fc2.com/blog-entry-2299.html)
[【初心者向け】PythonとPyxelでゲームプログラミングを始める一番簡単な方法](https://tkitao.hatenablog.com/entry/2020/05/05/133511)