2026年9月12日土曜日

師匠の難題8

〆たばかりだったのですが、前回アップしたソースを修正して一つ気づいた部分がありまして…
どうせやるならタイルコピーによる疑似アルファ処理を追加しようかな?と。
タイル処理による疑似アルファとはなんぞや?という話ですが、コレは90年代に見られた処理で…以前行った大カプコン展でもちらっと紹介されてました。

50%タイル

25%タイル

はい、2×2ドットの範囲で転送するドットとしないドットを決める事で疑似的に半透明っぽく見せる処理です。
又、表示するドットをずらす事で重なりを表現する事も可能です…
こうしてみると、全て転送した時に比べて画像のノイジーさがあまり気にならなくなりますね…

コードはかなり冗長になりますので…定数定義の部分と RollBlt クラスのみ抜粋

ソースここから

import pyxel

SCREEN_SIZE_W = 160
SCREEN_SIZE_H = 120
CENTER_X = 80
CENTER_Y = 60
EMPTY = -32767

ALPHA_NON       = 0
ALPHA_HALF_1    = 1
ALPHA_HALF_2    = 2
ALPHA_QUARTER_1 = 3
ALPHA_QUARTER_2 = 4
ALPHA_QUARTER_3 = 5
ALPHA_QUARTER_4 = 6

中略:第7回参照

class RollBlt:
    
    def __init__(self):
        self.buff = []
        for i in range(SCREEN_SIZE_H):
            self.buff.append(LineBuffer())

        self.top = 0
        self.bottom = 0

        self.vbre = VBresenham()
        self.bre  = Bresenham()

    def setup(self):
        pass
    
    # 原点座標中央位置補正コピー    
    def blt_c(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle, scale_w=128, scale_h=128,alpha=ALPHA_NON):
        h = src_h * scale_h >> 8
        w = src_w * scale_w >> 8
        
        x = dest_x - my_sin(angle,h) - my_sin(angle+32,w)
        y = dest_y - my_cos(angle,h) - my_cos(angle+32,w)
        self.blt(x, y, bank, src_x, src_y, src_w, src_h, angle, scale_w, scale_h, alpha)
        
    def blt(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle, scale_w=128, scale_h=128, alpha=ALPHA_NON):

        # 角度のまるめ
        angle &= 127

        # 原点から見た縦方向の端の座標
        h = src_h * scale_h >> 7
        tv_x = my_sin(angle,h-1)+dest_x
        tv_y = my_cos(angle,h-1)+dest_y

        # 原点から見た横方向の端の座標
        w = src_w * scale_w >> 7
        th_x = my_sin(angle+32,w-1)+dest_x
        th_y = my_cos(angle+32,w-1)+dest_y
        
        # 4頂点と比較し上下の座標を求める
        self.top = dest_y
        if tv_y < self.top:
            self.top = tv_y
        if th_y < self.top:
            self.top = th_y
        if (tv_y + th_y - dest_y) < self.top:
            self.top = tv_y + th_y - dest_y
        if self.top < 0:
            self.top = 0

        self.bottom = dest_y
        if tv_y > self.bottom:
            self.bottom = tv_y
        if th_y > self.bottom:
            self.bottom = th_y
        if (tv_y + th_y - dest_y) > self.bottom:
            self.bottom = tv_y + th_y - dest_y
        if self.bottom > (SCREEN_SIZE_H-1):
            self.bottom = SCREEN_SIZE_H-1

        # テーブル初期化
        for y in range (self.top, self.bottom+1):
            self.buff[y].l = EMPTY
            self.buff[y].r = EMPTY

        # 画像縦方向処理
        reg = self.vbre.setup(dest_x,dest_y,tv_x,tv_y)
        self.bre.setup(src_h-1,reg-1)

        match angle:
            # 垂直にコピーする時は縦方向の処理を行わない
            case a if a == 32 or a == 96:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()

                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:
                        
                        if self.buff[y].l == EMPTY:
                            self.buff[y].l = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x
                        self.buff[y].src_sy = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].r = x
                        #pyxel.pset(x,y,6)
                        
                        self.buff[y].src_tx = src_x + src_w-1
                        self.buff[y].src_ty = src_y + add_src_y

            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].r = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x
                        self.buff[y].src_ty = src_y + add_src_y
                        
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].l == EMPTY:
                            self.buff[y].l = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + src_w-1
                        self.buff[y].src_sy = src_y + add_src_y
            
            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].r == EMPTY:
                            self.buff[y].r = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x
                        self.buff[y].src_ty = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].l = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + src_w-1
                        self.buff[y].src_sy = src_y + add_src_y

            case _:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].l = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x
                        self.buff[y].src_sy = src_y + add_src_y

                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].r == EMPTY:
                            self.buff[y].r = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + src_w-1
                        self.buff[y].src_ty = src_y + add_src_y

        # 画像横方向処理
        reg = self.vbre.setup(dest_x,dest_y,th_x,th_y)
        self.bre.setup(src_w-1,reg-1)

        match angle:
            # 水平にコピーする時は横方向の処理を行わない
            case a if angle == 0 or angle == 64:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                    
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].l == EMPTY:
                            self.buff[y].l = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + add_src_x
                        self.buff[y].src_sy = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].r = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + add_src_x
                        self.buff[y].src_ty = src_y + src_h-1
                
            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].l = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + add_src_x
                        self.buff[y].src_sy = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].r == EMPTY:
                            self.buff[y].r = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + add_src_x
                        self.buff[y].src_ty = src_y + src_h-1

            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()

                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].r == EMPTY:
                            self.buff[y].r = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + add_src_x
                        self.buff[y].src_ty = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].l = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + add_src_x
                        self.buff[y].src_sy = src_y + src_h-1

            case _:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                    
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].r = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + add_src_x
                        self.buff[y].src_ty = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].l == EMPTY:
                            self.buff[y].l = x
                        #pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + add_src_x
                        self.buff[y].src_sy = src_y + src_h-1

        # バッファを基に転送を行う

        # タイル処理なし
        if alpha == ALPHA_NON:
            for y in range (self.top, self.bottom+1):
                reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
                self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
                
                sx,sy = self.vbre.step()
                old_src = 0
                new_src = 0

                for x in range (self.buff[y].l,self.buff[y].r+1):
                    new_src = self.bre.step()
                    for step_src in range (old_src, new_src):
                        sx,sy = self.vbre.step()
                    old_src = new_src
                    
                    # 画面左右端処理
                    if x < 0:
                        continue
                    if x >= SCREEN_SIZE_W:
                        break
                    
                    pyxel.blt(x, y, 0, sx, sy, 1, 1)

        # タイル処理50% 1
        if alpha == ALPHA_HALF_1:
            for y in range (self.top, self.bottom+1):
                reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
                self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
                
                sx,sy = self.vbre.step()
                old_src = 0
                new_src = 0

                for x in range (self.buff[y].l,self.buff[y].r+1):
                    new_src = self.bre.step()
                    for step_src in range (old_src, new_src):
                        sx,sy = self.vbre.step()
                    old_src = new_src
                    
                    # 画面左右端処理
                    if x < 0:
                        continue
                    if x >= SCREEN_SIZE_W:
                        break
                        
                    if x&1 == y&1:
                        pyxel.blt(x, y, 0, sx, sy, 1, 1)

        # タイル処理50% 2
        if alpha == ALPHA_HALF_2:
            for y in range (self.top, self.bottom+1):
                reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
                self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
                
                sx,sy = self.vbre.step()
                old_src = 0
                new_src = 0

                for x in range (self.buff[y].l,self.buff[y].r+1):
                    new_src = self.bre.step()
                    for step_src in range (old_src, new_src):
                        sx,sy = self.vbre.step()
                    old_src = new_src
                    
                    # 画面左右端処理
                    if x < 0:
                        continue
                    if x >= SCREEN_SIZE_W:
                        break
                        
                    if x&1 != y&1:
                        pyxel.blt(x, y, 0, sx, sy, 1, 1)

        # タイル処理25% 1
        if alpha == ALPHA_QUARTER_1:
            for y in range (self.top, self.bottom+1):
                reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
                self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
                
                sx,sy = self.vbre.step()
                old_src = 0
                new_src = 0

                for x in range (self.buff[y].l,self.buff[y].r+1):
                    new_src = self.bre.step()
                    for step_src in range (old_src, new_src):
                        sx,sy = self.vbre.step()
                    old_src = new_src
                    
                    # 画面左右端処理
                    if x < 0:
                        continue
                    if x >= SCREEN_SIZE_W:
                        break
                        
                    if (x&1) + (y&1)==0:
                        pyxel.blt(x, y, 0, sx, sy, 1, 1)

        # タイル処理25% 2
        if alpha == ALPHA_QUARTER_2:
            for y in range (self.top, self.bottom+1):
                reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
                self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
                
                sx,sy = self.vbre.step()
                old_src = 0
                new_src = 0

                for x in range (self.buff[y].l,self.buff[y].r+1):
                    new_src = self.bre.step()
                    for step_src in range (old_src, new_src):
                        sx,sy = self.vbre.step()
                    old_src = new_src
                    
                    # 画面左右端処理
                    if x < 0:
                        continue
                    if x >= SCREEN_SIZE_W:
                        break
                        
                    if ((x+1)&1) + (y&1)==0:
                        pyxel.blt(x, y, 0, sx, sy, 1, 1)

        # タイル処理25% 3
        if alpha == ALPHA_QUARTER_3:
            for y in range (self.top, self.bottom+1):
                reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
                self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
                
                sx,sy = self.vbre.step()
                old_src = 0
                new_src = 0

                for x in range (self.buff[y].l,self.buff[y].r+1):
                    new_src = self.bre.step()
                    for step_src in range (old_src, new_src):
                        sx,sy = self.vbre.step()
                    old_src = new_src
                    
                    # 画面左右端処理
                    if x < 0:
                        continue
                    if x >= SCREEN_SIZE_W:
                        break
                        
                    if (x&1) + ((y+1)&1)==0:
                        pyxel.blt(x, y, 0, sx, sy, 1, 1)

        # タイル処理25% 4
        if alpha == ALPHA_QUARTER_4:
            for y in range (self.top, self.bottom+1):
                reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
                self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
                
                sx,sy = self.vbre.step()
                old_src = 0
                new_src = 0

                for x in range (self.buff[y].l,self.buff[y].r+1):
                    new_src = self.bre.step()
                    for step_src in range (old_src, new_src):
                        sx,sy = self.vbre.step()
                    old_src = new_src
                    
                    # 画面左右端処理
                    if x < 0:
                        continue
                    if x >= SCREEN_SIZE_W:
                        break
                        
                    if (x&1) + (y&1)==2:
                        pyxel.blt(x, y, 0, sx, sy, 1, 1)

class App:

    def __init__(self):
        # 画面サイズ 160x120 で初期化
        pyxel.init(SCREEN_SIZE_W, SCREEN_SIZE_H, title="RollBlt")
        self.add_x = 0
        pyxel.load("yajirushi.pyxres")

        self.rb = RollBlt()
        
        self.angle  = 0
        self.angle2 = 0

        pyxel.run(self.update, self.draw)

    def update(self):
        self.angle += 1
        self.angle &= 127
        
        self.angle2 -= 1
        self.angle2 &= 127

    def draw(self):
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)
        
        #self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=64,scale_h=64)
        #self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=256,scale_h=64)
        ##
        """
        self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=512,scale_h=512, alpha=ALPHA_HALF_1)
        self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle2,scale_w=512,scale_h=512, alpha=ALPHA_HALF_2)
        """
        self.rb.blt_c(CENTER_X-30, CENTER_Y-20, 0, 0, 0, 32, 16, self.angle,scale_w=512,scale_h=512, alpha=ALPHA_QUARTER_1)
        self.rb.blt_c(CENTER_X-30, CENTER_Y+20, 0, 0, 0, 32, 16, self.angle2,scale_w=512,scale_h=512, alpha=ALPHA_QUARTER_4)
        self.rb.blt_c(CENTER_X+30, CENTER_Y-20, 0, 0, 0, 32, 16, self.angle,scale_w=512,scale_h=512, alpha=ALPHA_QUARTER_2)
        self.rb.blt_c(CENTER_X+30, CENTER_Y+20, 0, 0, 0, 32, 16, self.angle2,scale_w=512,scale_h=512, alpha=ALPHA_QUARTER_3)

App()


ソースここまで

各 ALPHA が if 文で括られていますが違いは Pyxel.Blt を呼ぶかの判定式のみ
ただ、再三書いている通り、ループの中に分岐を書くのでは無く、分岐の中にループを書かないと処理が重くなるのでこのような冗長な記述になります。
…というか、この4重アルファの時点で Celeron 2コアマシンでは処理落ちが始まるのですよ…以前、処理の遅さを感じられるからプログラミングの訓練にはロースペックマシンが良いと書いたのですが、いきなり実感する事になりました。
さすがにこれ以上の発展は…やり始めるとパレットモードで3Dモデルを表示するだのおかしな話になりますし、そうなると Python より C++ で書かせろという話になります。
いや、ゲーム自体を作れば…は流石に何年かかるか判りませんし…又なにかあったら追記するかもしれませんが、ひとまずこれでホントの締めになると思います。

2026年9月11日金曜日

師匠の難題7

はい、今回予告通り〆となります。

ソースここから

import pyxel

SCREEN_SIZE_W = 160
SCREEN_SIZE_H = 120
CENTER_X = 80
CENTER_Y = 60
EMPTY = -32767

my_table = (
省略:第5回参照

def my_sin(angle,r):
省略:第5回参照
    
def my_cos(angle,r):
省略:第5回参照

class VBresenham:
省略:第5回参照

class Bresenham:
省略:第5回参照

class LineBuffer:
省略:第5回参照

class RollBlt:
    
    def __init__(self):
        self.buff = []
        for i in range(SCREEN_SIZE_H):
            self.buff.append(LineBuffer())

        self.top = 0
        self.bottom = 0

        self.vbre = VBresenham()
        self.bre  = Bresenham()

    def setup(self):
        pass
    
    # 原点座標中央位置補正コピー    
    def blt_c(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle, scale_w=128, scale_h=128):
        h = src_h * scale_h >> 8
        w = src_w * scale_w >> 8
        
        x = dest_x - my_sin(angle,h) - my_sin(angle+32,w)
        y = dest_y - my_cos(angle,h) - my_cos(angle+32,w)
        self.blt(x, y, bank, src_x, src_y, src_w, src_h, angle, scale_w, scale_h)
        
    def blt(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle, scale_w=128, scale_h=128):

        # 角度のまるめ
        angle &= 127

        # 原点から見た縦方向の端の座標
        h = src_h * scale_h >> 7
        tv_x = my_sin(angle,h-1)+dest_x
        tv_y = my_cos(angle,h-1)+dest_y

        # 原点から見た横方向の端の座標
        w = src_w * scale_w >> 7
        th_x = my_sin(angle+32,w-1)+dest_x
        th_y = my_cos(angle+32,w-1)+dest_y
        
        # 4頂点と比較し上下の座標を求める
        self.top = dest_y
        if tv_y < self.top:
            self.top = tv_y
        if th_y < self.top:
            self.top = th_y
        if (tv_y + th_y - dest_y) < self.top:
            self.top = tv_y + th_y - dest_y
        if self.top < 0:
            self.top = 0

        self.bottom = dest_y
        if tv_y > self.bottom:
            self.bottom = tv_y
        if th_y > self.bottom:
            self.bottom = th_y
        if (tv_y + th_y - dest_y) > self.bottom:
            self.bottom = tv_y + th_y - dest_y
        if self.bottom > (SCREEN_SIZE_H-1):
            self.bottom = SCREEN_SIZE_H-1

        # テーブル初期化
        for y in range (self.top, self.bottom+1):
            self.buff[y].l = EMPTY
            self.buff[y].r = EMPTY

        # 画像縦方向処理
        reg = self.vbre.setup(dest_x,dest_y,tv_x,tv_y)
        self.bre.setup(src_h-1,reg-1)

        match angle:
            # 垂直にコピーする時は縦方向の処理を行わない
            case a if a == 32 or a == 96:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()

                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:
                        
                        if self.buff[y].l == EMPTY:
                            self.buff[y].l = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x
                        self.buff[y].src_sy = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].r = x
                        pyxel.pset(x,y,6)
                        
                        self.buff[y].src_tx = src_x + src_w-1
                        self.buff[y].src_ty = src_y + add_src_y

            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].r = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x
                        self.buff[y].src_ty = src_y + add_src_y
                        
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].l == EMPTY:
                            self.buff[y].l = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + src_w-1
                        self.buff[y].src_sy = src_y + add_src_y
            
            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].r == EMPTY:
                            self.buff[y].r = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x
                        self.buff[y].src_ty = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].l = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + src_w-1
                        self.buff[y].src_sy = src_y + add_src_y

            case _:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].l = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x
                        self.buff[y].src_sy = src_y + add_src_y

                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].r == EMPTY:
                            self.buff[y].r = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + src_w-1
                        self.buff[y].src_ty = src_y + add_src_y

        # 画像横方向処理
        reg = self.vbre.setup(dest_x,dest_y,th_x,th_y)
        self.bre.setup(src_w-1,reg-1)

        match angle:
            # 水平にコピーする時は横方向の処理を行わない
            case a if angle == 0 or angle == 64:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                    
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].l == EMPTY:
                            self.buff[y].l = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + add_src_x
                        self.buff[y].src_sy = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].r = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + add_src_x
                        self.buff[y].src_ty = src_y + src_h-1
                
            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].l = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + add_src_x
                        self.buff[y].src_sy = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].r == EMPTY:
                            self.buff[y].r = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + add_src_x
                        self.buff[y].src_ty = src_y + src_h-1

            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()

                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].r == EMPTY:
                            self.buff[y].r = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + add_src_x
                        self.buff[y].src_ty = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].l = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + add_src_x
                        self.buff[y].src_sy = src_y + src_h-1

            case _:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                    
                    # MainSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        self.buff[y].r = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_tx = src_x + add_src_x
                        self.buff[y].src_ty = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    # SubSide
                    if y >= 0 and y < SCREEN_SIZE_H:

                        if self.buff[y].l == EMPTY:
                            self.buff[y].l = x
                        pyxel.pset(x,y,6)

                        self.buff[y].src_sx = src_x + add_src_x
                        self.buff[y].src_sy = src_y + src_h-1

        # バッファを基に転送を行う
        for y in range (self.top, self.bottom+1):
            reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
            self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
            
            sx,sy = self.vbre.step()
            old_src = 0
            new_src = 0

            for x in range (self.buff[y].l,self.buff[y].r+1):
                new_src = self.bre.step()
                for step_src in range (old_src, new_src):
                    sx,sy = self.vbre.step()
                old_src = new_src
                
                # 画面左右端処理
                if x < 0:
                    continue
                if x >= SCREEN_SIZE_W:
                    break
                
                pyxel.blt(x, y, 0, sx, sy, 1, 1)


class App:

    def __init__(self):
        # 画面サイズ 160x120 で初期化
        pyxel.init(SCREEN_SIZE_W, SCREEN_SIZE_H, title="RollBlt")
        self.add_x = 0
        pyxel.load("yajirushi.pyxres")

        self.rb = RollBlt()
        
        self.angle = 0

        pyxel.run(self.update, self.draw)

    def update(self):
        self.angle += 1
        self.angle &= 127

    def draw(self):
        # 画面を黒(色番号0)でクリア
        pyxel.cls(0)
        
        #self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=64,scale_h=64)
        #self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=256,scale_h=64)
        self.rb.blt_c(CENTER_X, CENTER_Y, 0, 0, 0, 32, 16, self.angle,scale_w=512,scale_h=512)

App()

ソースここまで

※画面左右端処理を追記
128を基準値とした拡縮/新設した blt_c 関数による中心指定コピー、上下画面外への処理も可能に。ちなみに前回の状態で画面上下にはみ出しても大丈夫ではありました…これは Python の仕様上ラインバッファが配列では無くリストになっていた為、インデックスがマイナスになってもエラーを吐かない為です。とは言え、それを頼ってはいけないので処理は追加しましたが。
拡大すると45度付近で顕著にズレが生じる事が解ります。なにやら古いDVDのようですね…法則性があるので解決策もありそうですが…

今回長方形の回転を作っていたので、Blt 関数内の各 case 文で原点を中心に辺の計算をした後で、原点を平行移動させて対になる辺の値も作っていました。
角度と長さが同じと判っている為、こうすれば無駄がない為です(各 MainSide SubSide 参照)。
しかし、師匠の最終課題とは…恐らく4頂点を自由移動…つまりポリゴン化だったのではないかと推測します。

セガサターンのポリゴンは「スプライトを変形させた疑似ポリゴン」

…という解説を読んだことはないでしょうか?

疑似も何もポリゴンは多角形の事なので4角形ならポリゴンなのですが。
つまり、これと同様のものを作れ…というのが最終課題だったのではないか?と、思われます。ただ、これは結構な問題で3角形は数学上折れる事は無いのですが4角形は折れたり自分自身と重なったりします。3頂点の中に他の頂点が含まれたか?の判定については、カリング判定式というものがあり、これも実数も割り算も使わない古いやり方があるのですが…

最後に白状してしまうと…
四半世紀以上昔の話、一度ゲーム業界を辞めて、一般ソフトハウスに入り、その後もう一度ゲーム業界にカムバックしたのですが。
その際にウィンドウズで DirectX を使わない独自ゲームライブラリを作って、サンプルデモを持ち込み転職した事がありました。
描画関係はほぼ i386 アセンブラ(一部 MMX Pentium )を使用しており、なんならアセンブラ内部からC++のメンバー関数を呼ぶといった器用な事もやっていたのですが…
画像転送、アニメーション有りスプライト処理(拡縮はできたが回転はできず)の他に…256色パレットモードビットマップを使った3D処理を作っており、六角大王で作ったデータを変換して3Dアニメーションデモを作る…といった処理も組んでいました。
ええ、テクスチャ無しなら4角だろうがポリゴンだろうが円だろうが描画する処理自体は組んだ事があり、それらをZソートで繋いで表示する事くらいはやっていたのです。
当時ビットマップ回転処理だけできなかったのが心残りでした。

まあ、そんなものを作る性分ですから DirectX どころかアンリアルエンジンやらのライブラリの「借り方」が上手い奴が上に行く当時の風潮や、とにかく新しい言語を覚え続けないといけないという世界に嫌気がさしてしまったんですけどね。
当時の風潮になじめなかったアセンブラバカを始めとするエンジニアの安息の地はどこにあったのでしょうね?久々に真面目にプログラムを組んでそんな事を思いました。

というわけで、実数と割り算を使用しないレトロ縛りの回転画像転送処理を一旦〆たいと思います。本当は年末までかかるかと思ったのですが、一気にやらないと頭から抜け落ちそうだったので押し通しました。
最後に、ゲーム業界でプログラマー1年目だった自分にこれだけの奥の深い課題を出してくれた師匠に最大限の感謝を。

2026年9月10日木曜日

イベ終了

だいぶ前に書いた通り、今回は甲乙丁丁乙での攻略。しかもE5乙の攻略中に半月延長のアナウンスを見ると言う状況でした…

水着ラ級…上記の通りのクリア状況だったので、初めて見たのは最終日前日にE1-3を掘りに戻った時でした

いや、初めから2カ月と知っていればやりようはあったのですが…
と言う訳で報酬は…装備面はどうにもならなかったのですが獲れた艦はかなりの数になります…
新艦全てと前回イベントで獲れなかったグロリアス、ワスプ、樫。
それ以外は…

アイオワ(3隻目)リシュリュー(3隻目)メリーランド(2隻目)インディアナ(2隻目)
レキシントン(2隻目)イントレピット(2隻目)ビクトリアス(2隻目)アクィラ(2隻目)(3隻目)サラトガ(3隻目)ラングレイ(2隻目)
ヒューストン(6隻目)ポーラ(4隻目)
アブルッツィ(3隻目)ガルバルディ(3隻目)シェフィールド(2隻目)
冬月(2隻目)ジェイナス(2隻目)マエストラーレ(5隻目)(6隻目)夏雲(2隻目)
伊201(4隻目) 伊36(2隻目) 伊400(4隻目)(5隻目)伊14(3隻目)カッペリーニ(2隻目)

クリアしてから1カ月以上掘りだった事もあり、色が付いている艦は既に改になっています。
E3の周回が長かったせいかアメリカ艦の層がかなり厚くなっていますね…。
潜水艦は…本体より晴嵐狙いではありますが、それなりに大漁です。

欧州方面に関してはコレクションした艦が報われはしましたが…
育てた艦が報われたかと言いますと…

…うーん。
ちなみに、伊勢と日向は誤出撃、アイオワはクリア後のE3掘りに出していましたが2隻目のインディアナが出た事もあり直ぐに引っ込める事に。
これも、情報が早ければサブ以外の大和も出せたし陸奥や長門の出番はあったと思いますし、サブのみで組んで出したイギリス艦隊も本命を出せたのでは。

何年か前に書きましたが、ウチは南雲機動部隊の改二空母が5セット居ます。
そんなものがフル出場になるイベントは御免こうむりますが…出番があったのは改にもなっていない拾ったばかりの海外空母でしたね…

31戦隊はともかく夕雲型は推していた割に出番が…
はい、これくしょんは報われましたが、育成する意味があるのか?とは思いました。
通常海域でも4以下と5以上及びEO以上の海域では命中率/回避率があからさまに違います。どんなにレベルを上げて当たらなければ意味がない/食らえばアウトの世界で、命中/回避をテキトーにイベントに組み込まれてはどこまで行っても運ゲーです。
夜戦カットインに関してもLv157以降はパラメータが上昇しないと言われていますが、流石に育てる意味あるのかな?とは思いました。



情報の後出しは悪
資材が無かったのもそうですが、1カ月半で掘り艦6と聴けば低難易度でクリアして掘りに集中という人も一定数出るとは思うのですが…

後出し延長

いや、開始だって4日延長したし、延長を読めない方が悪いと言う話もあるのですが「地震を理由にイベントを半月延長」なんて物が読めるなら気象庁に勤めていると思いますよ。
前々から先行勢はかわいそうだと書いていましたが、自分が準先行ともなると本当に酷さを実感します。
日枝丸も行き当たりばったりな投入でしたし、こんな場当たり的なイベントに2か月以上もつき合った提督達は本当にお疲れ様です。
そして情報提供者潰し機能(高レベル提督が低難度海域に出撃できない期間の実装)だけきっちり実装した運営は何を考えているのやら。

今後は北上改三の投入だそうですが…
…古参提督狙い撃ちです。16年辺りにプレーしていた層はハイパーズを複数組作っていたりしますからね…以前の飛竜改三も同様で、蒼龍と共に江草/友永牧場で古い提督は複数改二を作っている可能性がありました。
ただ、そもそもが「お仕置きルート」常連の北上が改三になってもイベントで活躍できるかは謎です。あと、雷巡の中では恐らく木曽が改三になれないので置いていかれる事になりますね(むしろ阿武隈が雷巡になるかも)。
※追記
バーナー550ですか…いや、持ってましたけどね、ギリギリ。川内型改二が気の毒になる5スロに生まれ変わりました。うーん、強い…けど、コレお仕置きコース確定なのでは…

何を売りたいのやら
今回も投入した日本艦は小型艦ばかり、何年も前から在庫切れ感がでています。最近は「味方が増やせないなら敵を売ろう」というラ級を始めとした敵を売る流れで来ていますが…滑ってますよね。
毎回喋って演出出されても、下手すると3桁周回しないといけないゲーム性と致命的に合わない。
どうしても売り込みたいなら敵艦を図鑑化してそちらで閲覧させる方法を実装すれば良いと思うのですけどね。
早く攻略する攻略法に「音声を切ってセリフをスキップする」とか書かれている時点で売り方を間違えていると思います。

2026年9月9日水曜日

師匠の難題6

 前回ソースコード全文を載せたので、今回は差分のみ。

ソースここから

class LineBuffer:
    def __init__(self):
        self.l = 0 # left
        self.r = 0 # right

        # 始点
        self.src_sx = 0
        self.src_sy = 0
        
        # 終点
        self.src_tx = 0
        self.src_ty = 0

class RollBlt:
    
    def __init__(self):
        self.buff = []
        for i in range(SCREEN_SIZE_H):
            self.buff.append(LineBuffer())

        self.top = 0
        self.bottom = 0

        self.vbre = VBresenham()
        self.bre  = Bresenham()

    def setup(self):
        pass
        
    def blt(self,dest_x, dest_y, bank, src_x, src_y, src_w, src_h, angle):

        # 角度のまるめ
        angle &= 127

        # 原点から見た縦方向の端の座標
        tv_x = my_sin(angle,src_h-1)+dest_x
        tv_y = my_cos(angle,src_h-1)+dest_y

        # 原点から見た横方向の端の座標
        th_x = my_sin(angle+32,src_w-1)+dest_x
        th_y = my_cos(angle+32,src_w-1)+dest_y
        
        # 4頂点と比較し上下の座標を求める
        self.top = dest_y
        if tv_y < self.top:
            self.top = tv_y
        if th_y < self.top:
            self.top = th_y
        if (tv_y + th_y - dest_y) < self.top:
            self.top = tv_y + th_y - dest_y
        if self.top < 0:
            self.top = 0

        self.bottom = dest_y
        if tv_y > self.bottom:
            self.bottom = tv_y
        if th_y > self.bottom:
            self.bottom = th_y
        if (tv_y + th_y - dest_y) > self.bottom:
            self.bottom = tv_y + th_y - dest_y
        if self.bottom > (SCREEN_SIZE_H-1):
            self.bottom = SCREEN_SIZE_H-1

        # テーブル初期化
        for y in range (self.top, self.bottom+1):
            self.buff[y].l = EMPTY
            self.buff[y].r = EMPTY

        # 画像縦方向処理
        reg = self.vbre.setup(dest_x,dest_y,tv_x,tv_y)
        self.bre.setup(src_h-1,reg-1)

        match angle:
            # 垂直にコピーする時は縦方向の処理を行わない
            case a if a == 32 or a == 96:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x
                    self.buff[y].src_sy = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + src_w-1
                    self.buff[y].src_ty = src_y + add_src_y

            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x
                    self.buff[y].src_ty = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + src_w-1
                    self.buff[y].src_sy = src_y + add_src_y
            
            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                    
                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x
                    self.buff[y].src_ty = src_y + add_src_y
                    
                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + src_w-1
                    self.buff[y].src_sy = src_y + add_src_y

            case _:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_y = self.bre.step()
                
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x
                    self.buff[y].src_sy = src_y + add_src_y

                    x = x - dest_x + th_x
                    y = y - dest_y + th_y
                    
                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + src_w-1
                    self.buff[y].src_ty = src_y + add_src_y

        # 画像横方向処理
        reg = self.vbre.setup(dest_x,dest_y,th_x,th_y)
        self.bre.setup(src_w-1,reg-1)

        match angle:
            # 水平にコピーする時は横方向の処理を行わない
            case a if angle == 0 or angle == 64:
                pass
                
            case a if a < 32:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                    
                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + add_src_x
                    self.buff[y].src_sy = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + add_src_x
                    self.buff[y].src_ty = src_y + src_h-1
                
            case a if a < 64:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                
                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + add_src_x
                    self.buff[y].src_sy = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + add_src_x
                    self.buff[y].src_ty = src_y + src_h-1

            case a if a < 96:

                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()

                    if self.buff[y].r == EMPTY:
                        self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + add_src_x
                    self.buff[y].src_ty = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + add_src_x
                    self.buff[y].src_sy = src_y + src_h-1

            case _:
                
                for i in range (reg):
                    x,y = self.vbre.step()
                    add_src_x = self.bre.step()
                    
                    self.buff[y].r = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_tx = src_x + add_src_x
                    self.buff[y].src_ty = src_y
                    
                    x = x - dest_x + tv_x
                    y = y - dest_y + tv_y

                    if self.buff[y].l == EMPTY:
                        self.buff[y].l = x
                    pyxel.pset(x,y,6)

                    self.buff[y].src_sx = src_x + add_src_x
                    self.buff[y].src_sy = src_y + src_h-1

        # バッファを基に転送を行う
        for y in range (self.top, self.bottom+1):
            reg = self.vbre.setup(self.buff[y].src_sx,self.buff[y].src_sy,self.buff[y].src_tx,self.buff[y].src_ty)
            self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)
            
            sx,sy = self.vbre.step()
            old_src = 0
            new_src = 0

            for x in range (self.buff[y].l,self.buff[y].r+1):
                new_src = self.bre.step()
                for step_src in range (old_src, new_src):
                    sx,sy = self.vbre.step()
                old_src = new_src
                
                pyxel.blt(x, y, 0, sx, sy, 1, 1)

ソースココまで

書き換えたのは LineBuffer クラスと RollBlt クラスのみです。

LineBuffer の (sx,sy) (tx,ty) が前回の説明の「どこから、どこ」の座標に当たります。
そして、それらをバンクに格納する処理は各 case 文の中に追記しましたが…
それぞれ全てを解説するのは無理なので端折りますが、転送元画像とバンク内の画像の座標は図の通りになっている事を念頭に読んでいただけたらと思います。

Blt 関数の最後で転送を行っています

for step_src in range (old_src, new_src):

このループですが、あらかじめ

self.bre.setup(reg-1,self.buff[y].r - self.buff[y].l)

という初期化を行っています。
これは転送先の幅を主体に、転送元の幅の現在位置を返す為のブレゼンハムアルゴリズムの初期化であり、動きがなければ値が変化せず、したがって for 文内は実行されません。
逆に値が1以上増える事もあり、その分 self.vbre.step() が呼ばれて転送元の座標が動いていく仕組みです。

そして出力結果は…
うーん。微妙なような、30年前の回転ってこんなモンだったような…
久しぶりに昔の写真を観たら思い出より美しく無かった時のような微妙な気分になる出来ですね。


さて、今回で見た目上は完成したかに見えますが…
この転送、横に画面外に出る分には元の pyxel.blt 関数がなんとかしてくれるのですが、縦に画面外に出てしまうと、画面の縦幅分しかラインバッファを用意していない関係で index error を吐いて止まってしまいます。
あと、どうせここまでやったら拡大/縮小をふやしたり、回転の中心を画像の中心にする転送も行いたい所です。
と言う事で次回は細かな修正とまとめ、そして以前書いた「師匠の本当の課題」とは?という部分を書いて〆たいと思います。