sfc-sdp / GameCanvas-Unity

慶應義塾大学『スマートデバイスプログラミング』教材 GameCanvas for Unity
http://web.sfc.keio.ac.jp/~wadari/sdp/
MIT License
46 stars 19 forks source link

gc.DrawLineで描ける線の数に上限がある? #90

Closed Chiji1108 closed 4 years ago

Chiji1108 commented 6 years ago

概要

大量に線を描く処理をした際、線が描画されなくなる

再現手順

下記のコードを実行した際、途中から線が描画されなくなるor古い線が消えていく

using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using Sequence = System.Collections.IEnumerator;

/// <summary>
/// ゲームクラス。
/// 学生が編集すべきソースコードです。
/// </summary>
public sealed class Game : GameBase
{
    class Resolution {
        public int x{get;} = 1920;
        public int y{get;} = 1080;
    }
    class Line {
        public int x{get;set;}
        public int y{get;set;}
        public int px{get;set;} = 10; //直径・太さ
        public int color {get;set;}
        public int preX{get;set;} = -1;
        public int preY{get;set;} = -1;
    }
    Resolution res = new Resolution();
    List<Line> lines = new List<Line>();

    /// <summary>
    /// 初期化処理
    /// </summary>
    public override void InitGame()
    {
        // キャンバスの大きさを設定します
        gc.SetResolution(res.x, res.y);
    }

    /// <summary>
    /// 動きなどの更新処理
    /// </summary>
    public override void UpdateGame()
    {
        if(gc.GetPointerFrameCount(0) > 0){
            Line line = new Line();
            if (lines.Count() > 0 && !gc.GetIsPointerBegan(0)) {
                line.preX = lines.Last().x;
                line.preY = lines.Last().y;
            }
            line.x = gc.GetPointerX(0);
            line.y = gc.GetPointerY(0);
            lines.Add(line);
        }
    }

    /// <summary>
    /// 描画の処理
    /// </summary>
    public override void DrawGame()
    {
        // 画面を白で塗りつぶします
        gc.ClearScreen();

        // デバッグ出力
        gc.SetColor(0, 0, 0);
        gc.SetFontSize(48);
        if (lines.Count() > 0) {
            gc.DrawString($"- デバッグ -\r\nX: {lines.Last().x}\r\nY: {lines.Last().y}", 50 , 50);
            // gc.DrawString($"preX: {lines.Last().preX}\r\npreY: {lines.Last().preY}",50,200);
        }

        // 線を描く
        foreach (Line l in lines) {
            if(!(l.preX <0 || l.preY < 0)) {
                DrawBoldLine(l.preX,l.preY,l.x,l.y,l.px);
            }
        }
    }

    /// <summary>
    /// 太さのある直線を描画します。
    /// </summary>
    /// <param name="startX">開始点のX座標</param>
    /// <param name="startY">開始点のY座標</param>
    /// <param name="endX">終了点のX座標</param>
    /// <param name="endY">終了点のY座標</param>
    /// <param name="px">線の太さ</param>
    public void DrawBoldLine(int startX, int startY, int endX, int endY, int px) {
        const int ROUGHNESS = 1;
        int outSideStartX;
        int outSideStartY;
        int outSideEndX;
        int outSideEndY;
        float center = ((float)px)/2;
        if (startX-endX == 0 && startY - endY == 0) { //点だった時
            return;
        }else if(startX-endX == 0) { //線が垂直だった時
            for (int i=0; i<px*ROUGHNESS; i++) {
                outSideStartX = Mathf.FloorToInt(startX-center+((float)i)/ROUGHNESS);
                outSideStartY = startY;
                outSideEndX = Mathf.FloorToInt(endX-center+((float)i)/ROUGHNESS);
                outSideEndY = startY;
                gc.DrawLine(outSideStartX,outSideStartY,outSideEndX,outSideEndY);
            }
        }else if (startY - endY == 0) { //線が水平だった時
            for (int i=0; i<px*ROUGHNESS; i++) {
                outSideStartX = startX;
                outSideStartY = Mathf.FloorToInt(startY+center-((float)i)/ROUGHNESS);
                outSideEndX = startX;
                outSideEndY = Mathf.FloorToInt(endY+center-((float)i)/ROUGHNESS);
                gc.DrawLine(outSideStartX,outSideStartY,outSideEndX,outSideEndY);
            }
        }else { //普通の時
            float slope = (-(startY-endY))/(startX-endX);
            float cross = -(1/slope);
            float angleRAdian = Mathf.Atan(cross);
            for (int i=0; i<px*ROUGHNESS; i++) {
                outSideStartX = Mathf.FloorToInt(startX- (Mathf.Cos(angleRAdian)*(center-((float)i)/ROUGHNESS)));
                outSideStartY = Mathf.FloorToInt(startY+ (Mathf.Sin(angleRAdian)*(center-((float)i)/ROUGHNESS)));
                outSideEndX = Mathf.FloorToInt(endX- (Mathf.Cos(angleRAdian)*(center-((float)i)/ROUGHNESS)));
                outSideEndY = Mathf.FloorToInt(endY+ (Mathf.Sin(angleRAdian)*(center-((float)i)/ROUGHNESS)));
                gc.DrawLine(outSideStartX,outSideStartY,outSideEndX,outSideEndY);
            }
        }
    }
}

実行環境

環境 バージョン
macOS 10.14
Unity 2018.2.10f1
GameCanvas 3.0.1
seibe commented 6 years ago

@Chiji1108 ご報告ありがとうございます。 現象について調査しましたが、GameCanvasの不具合ではなくUnityの仕様のようです。

エディタ上で確認した限りでは、2048を超える CommandBuffer については描画命令が発行されないように見えました。 スマートデバイス実機での上限については未確認ですが、これほどまでに大量の描画コマンドは性能上処理できないと思われます。

今回の場合、描画命令(gc.DrawLine)の呼び出し回数を減らす工夫が不可欠です。

89 で提案いただいた線の太さパラメータの追加が叶えばある程度削減はできますが、満足な描画性能を実現するには gc.DrawPolyline メソッドも必要そうです。

エンジンの機能追加については持ち帰って検討しますが、現状のGameCanvasのままで実現できないかどうか色々と工夫してみてください。 アイデアのヒント:リフレッシュレート、速度、円

Chiji1108 commented 6 years ago

@seibe 仕様確認本当にありがとうございます! やはりDrawLineはそんな連発していいものではないんですね 実機で軽い動作になるように作るとすると、やはり作り方が悩みます。 現状ではポインターが押されている所の座標を配列に入れて、そこから円を描画するというのもやりましたが、やはりリフレッシュレートが低い?ので早いポインターの動きをすると中間が描画されないんですよね 中間の座標に円を敷き詰めるのも良さそうですが、提示してくださった「アイデアのヒント」の「速度」が気になります… 自分でも考えてみますが、もう少しヒント頂けないでしょうか…?笑

Chiji1108 commented 6 years ago

@seibe 分かりました!gc.ClearScreenを毎フレーム呼び出さないって事ですね! これでだいぶ処理が軽くなると思います、ありがとうございました! そのほかの方法があったらぜひ教えてください…m( )m