Siv3Dでシェーダ入門する

2019/04/06

 Siv3Dを使うといろいろ楽にできる。 シェーダも楽なんじゃないかと期待して触り始めた。

ソース

zxc / shader1 · GitLab

 今のところ、Siv3Dでシェーダを触る記事自体があまりないため、結局MicrosoftのリファレンスなりUnityのシェーダ使用例を参照することになる。
 したがって、調べるという点ではあまり楽だとは思わない。関数群のやっていることや使い方も良くわからないし。
 ただこれはある程度触って基本的な流れがわかってくると、なんとなくでもどういうことを中でしているか想像できるようになってくるんじゃなかろうか。
 使うという点ではすごく楽でいい。 ほかの環境でシェーダ使ったことはないが、シェーダファイルとシェーダ利用部分だけに注力しやすい。

scrapbox.io

 公式のsampleで一番楽なのはおそらくこれ。
ただコピペだけでは動かないかもしれない。
自分が動かすためにやったメモ。


 とりあえずMSVCの新規作成でHLSLのテンプレートは7つ出てくる設定にする。
設定を書き換えれば1つのテンプレートからどのシェーダファイルも作れるのだろうが面倒だ。

VisualStudio の HLSL テンプレートがおかしい
VisualStudio の HLSL テンプレートがおかしい その2


 あとはファイルを作ってコピペする。
シェーダファイル(.hlsl)のプロパティからエントリポイントとシェーダの種類がそれぞれ、PSとピクセルシェーダになっていることを確認する。 デフォルトではエントリポイントがmainらしいので書き換える。
その後、コンパイルしていつも通りにプロジェクトを実行する。
 シェーダが画面に反映されていないなら、PixelShader::isEmpty()でファイルが読み込めているか確認。
 Siv3DプロジェクトのExampleフォルダには、いくつかシェーダが入っているので、そこのシェーダで試すのも良いかもしれない。



 実際に触る。  Siv3Dのサンプルのwhite.hlslはピクセルシェーダだ。
ピクセルシェーダはレンダリングパイプラインの中で、比較的下流に位置するプログラマブルシェーダだ。
画面に表示される直前にピクセルの中身を制御する。らしい。
(したがって画面上の広い範囲に適用したいならなるべく重い処理を書かないほうが良い。その大きさ分、ピクセルシェーダで計算することになる。)

 このページがさっさと読めてわかりやすい。
【シェーダ】レンダリングパイプライン

 サンプルでは線形補完(?)というものをしている。
高校の数学の教科書で見たような下の式が関係している。

  \vec{x} = \alpha * \vec{v1} + (1-\alpha) * \vec{v2}

 三角形OABを考える。ここで
  
\vec{v1} = \vec{OA},\
\vec{v2} = \vec{OB},\
0 \leq \alpha \leq 1

とすると、\vec{x}は三角形の辺AB上の任意の点まで、点Oから伸びるベクトルになるはず。
 もっと簡単に言うと点Aと点Bの間を滑らかに変化する点Xを表現するのに使える。のだろう。

 この\alphaがsample中のstrength変数にあたる。



 コンスタントバッファはシェーダに渡す変数をまとめた構造体。 これをもっとうまく使えば、任意の色のみの画像と元の画像とを行き来するようなシェーダを一つのシェーダファイルで実現できるのだろう。

21.コンスタントバッファの作成 | gameproject

今回reversed1等、一切使っていないのだが必要なのだろうか。
EndPS関数等、まだ良くわからない。

cmakeを使った雑記

 何度か心が折れているけど、それでもテスト自動化したい。

GitLabにpushすると自動でCIが回って云々してくれるのは分かった。 一方で色々わからない。定石がわからない。

  • GitLabでテストを呼ぶ
  • GitLabに呼べるようにテストを置く
  • そもそもローカルで自動化すべきでは

 どうせ1人の開発だろうし、ローカルでも良いと思う。 仮にpublicにするとしても、GitLabでのみテストするってのは効率が良いとは思えない。 (無料かつほぼデフォルトの設定だと高速ではないような) お金を払ったり、複数人で開発するなら全然良いんだろうけど。

 大抵調べるとCIしてるプロジェクトはUnix系?かMacOSでそのまま参考にはできない。 dockerも入れてコンテナ建てるpullするくらいは分かったけど、 ここに例えばubuntu入れてcmake+clang or gcc 入れて、 そこでコード書くかホストのWindowsから渡すようなことをするのか? 面倒さは増えてるような。

 良くわからないけどシンプルなのは手元で自動化したものをそのままGitLabに上げることじゃなかろうか。 色々あるんだろうけどGitLab/GithubのCIでcmakeを使っているらしいプロジェクトがあるのは知っている。 cmakeを使ってローカルでもある程度回せないのか?

 そんなわけでcmakeを触り始めた。適当にやってわかるものではないとわかったのでtutorialをする。 現在step2/7が終わった。ソースをⅮLするとtutorialのstepごとの結果も入っているので参考にできる。 でもstepが微妙にずれているように思う。(ソースだとstepが11まである)

 とりあえずビルドの産物をbuildディレクトリに隔離できるのは楽でいい。 ディレクトリ指定の誤りや誤字で簡単にコケるし、どこが間違っていると教えてくれるとも限らないのは厳しい。 それを踏まえるとなるべく短くわかりやすく書くのがいいんだろうかこれ。

プロジェクトのディレクトリにCMakeLists.txt と buildディレクトリ作ったら下の感じで良いっぽい。

$ ...\build>cmake ..
$ ...\build>cmake --build .

--buildにたどり着くまで結構かかった。 .slnをVisualStudioで開いてビルドとかでも良い。 調べた先ではmakeしてるから Windows MSVCだとmakeは何にあたるのか調べてnmakeだとかを新しく入れるとか.dat使うとか。 色々調べてcmake --help眺めてたら見つけた。

最終的には以下になるのか?

  • 未実施だったらテスト
  • テストが通った時だけmasterにcommit

Windowsで開発するのは、今まで思っていたよりもずっと面倒で大変なことなのかもしれない。 cmake終わったら折角入れたんだからdockerも使ってみようか。 ubuntu等手軽に触れるのはいい。

参考

CMAKE によるビルド自動化とテスト実行 - zonomasaの日記

CMakeの使い方(その1) - Qiita

上記以外にもいくつか参考にしたけど、特にディレクトリ関係がわかりやすいのをいくつか。

ABC120

ABC120

UnionFindがわからないなら以下を見ると良いかもしれない。
UnionFindの解説 : B: Union Find - AtCoder Typical Contest 001 | AtCoder

atcoder.jp

A問題

方針

  • 高橋くんが満足するほどお金を持っているなら C を出力
  • そうではないなら手持ちのお金 B を一回当たりの料金 A で割った値を出力
  • 言い換えればこれはCとB//Aの小さい方が答え。
  • つまりmin(C, B//A)したらいい
  • pythonで書くと楽

python

#python3 
a, b, c = map( int, input().split() ) 
print( min(b//a,c) )

C++

//C++
#include<iostream>
#include<algorithm>

int main() {
    int a, b, c;
    std::cin >> a >> b >> c;
    std::cout << std::min(b/a ,c) << "\n";
    return 0;
}

B問題:

方針

  • forで回してi( 1<= i <=100)がAとBの両方を割り切れるならvector等に追加
  • 最後に大きい方からK番目の要素を出力
  • forを降順で回す、vectorを降順ソートする、要素へのアクセスを調整のどれかをする
  • forを降順で回したりソートするのは何か違う気がするので、最後のアクセスで調整
  • 何も考えないでfor書いてしまうとi = 0の時にゼロ除算になる
  • ちょっと問題文で迷った
  • ( コード中の改行がおかしくなる。やたら広いかとか出てくる。)

python

#python 3
a, b, k = map(int,input().split())
ans = []

for i in range(1,min(a,b)+1):
    if a%i ==0 and b%i == 0:
        ans.append(i)

print(ans[-1*k])

C++

//cpp14
#include<iostream>
#include<vector>
#include<algorithm>

int main() {
    int a, b, k;
    std::cin >> a >> b >> k;

    std::vector<int> v;
    auto mv = std::min(a, b);

    for (int i{1}; i <= mv; ++i) {
        if (a%i == 0 &&; b%i == 0)
            v.emplace_back(i);
    }

    std::cout << *(v.rbegin() + k-1) << "\n";

    return 0;
}

C問題:

方針

  • ぷよぷよ的な落ちモノパズルゲームっぽい問題
  • 文字列Sについてfor回す。文字Siを文字列Sのi番目の文字とする
  • stackが空か、stackのtopがSiと同じ文字なら、文字を積む
  • それ以外の場合(=スタックに要素が1つ以上あり、topがSiと別の文字、つまりは隣合えば消える組み合わせ)
  • stackのtopとSiには消えてもらうstack.popとi+=1による要素飛ばしで実装
  • remで消えた文字数をカウント
  • ひどいコードだったのでpythonのコードを修正。提出してACを確認済み。

蛇足

  • 多分removeのremなんだろうがremainと紛らわしい
  • countのが良いんじゃないか
  • ssは、多分lenでも十分早いと思うけれど、何度も関数呼ぶよりも早いのかな、くらいの気持ち
  • 最適化は実測してからしたほうがいい
  • 制約から文字列Sの長さNが105以下だったので、とりあえず2重ループ書いたらそれがTLE
  • 反省してきちんと考えた結果、stackか最小値取る方法を思いつく
  • 最小値は証明とかできなかったのでstackで
  • 解説見る限り、最小値でも良いらしいし、提出したら通った。

python

#python 3

s = input()
n = len(s)
stack = [ s[0] ]
rem = 0

ss = len(stack)

for i in range(1,n):

    if ss==0 or stack[-1] == s[i]:

        stack.append(s[i])
        ss += 1
        continue

    stack.pop()
    ss -= 1
    i+=1
    rem +=2

print(rem)

C++

//cpp14
#include<iostream>
#include<vector>
#include<string>

int main() {

    std::string s;
    std::cin >> s;

    std::vector<char> stack_;
    stack_.reserve(s.size());
    auto count = 0;

    for (const auto&amp;e : s) {
        if (stack_.empty()) {
            stack_.push_back(e);
            continue;
        }           
        if (stack_.back() != e) {
            stack_.pop_back();
            count += 2;
        }
        else {
            stack_.push_back(e);
        }
    }

    std::cout << count << "\n";
    return 0;
}

C++ 別解

//cpp14 別解
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>

int main() {
    std::string s;
    std::cin >> s;

    decltype(s.size()) zeros = std::count(s.begin(), s.end(), '0');

    std::cout << 2*std::min(zeros, s.size() - zeros) << "\n";

    return 0;
}

D問題:

方針

  • グラフの辺を減らすのは難しい
  • 反対から考えて辺を増やしていくことを考える
  • 頂点をグループ化していくと考えるとUnionFindが使える
  • UnionFindでグループ化した任意のグループが含んでいる頂点の数がわかると答えが出せる

コンテスト中の考えとか

  • コンテスト中はグラフの分割なんてわからん! で諦めた。
  • 解説によるとグラフから辺を削除するのは確かに難しいらしい。
  • 私でなくてもそうなら、これはとても大事なこと。
  • 最後の答えの値がn*(n-1)//2になることとUnionFindっぽい?とは思った。
  • 入力を配列に入れて逆順でーは思いつかなかった残念。
  • 解説見たら手を出して良い問題だった。多分WAするが
  • C++pythonも人のコードを参考に書いたもの
  • size関数がいることは分かったけど内部に配列一つ持つだけで実装できるとは
  • 全然思いつかないし、実装できる気がしないぞ。未だにちょっとわかってない。

実装概要、多分だけど

size

  • 任意の木の大きさはその根に入っている
  • 根以外は親のindexを持っている
  • したがって木の大きさは-par[root(n)]
  • 「-」するのは大きさが負の整数で表現されているため
  • 大きさは常に負。初期値は-1で、増えるケースは大きさ同士の加算のみ
  • uniteされる2つの木の大きい方の根が大きさを持つ

root

  • parの初期値が-1なので、値が負なら自身が根ということでnを返す
  • それ以外なら経路圧縮しつつ親の値を得て返す
  • 再帰難しい

__link__

  • 引数のa,bはrootされていることを前提に実装。
  • aとbが等しいなら同じグループなのでreturn
  • size(a)とsize(b)を比較してどちらが根になるべきか判定
  • 一度でも根ではなくなったものは二度と根になることはない
  • (辺が削除されず追加される一方ということはそういうことだろう)
  • つまりは根には-1か、それらが加算されてきた負の値が入っているはず
  • より大きい木が大きさの値を受け継いで、小さい木の根は親を指す

python

# python 3

class dset:
    def __init__(self,n):
        self.par = [-1]*n

    def size(self,n):
        return -self.par[self.root(n)]

    def root(self,n):
        if self.par[n]<0:
            return n
        else:
            self.par[n] = self.root(self.par[n])
            return self.par[n]

    def same(self, a, b):
        return self.root(a) == self.root(b)

    def __link__(self,a,b):
        if a == b:
            return 

        if self.size(a) < self.size(b):
            a, b = b, a
        self.par[a] += self.par[b]
        self.par[b] = a

    def unite(self,a,b):
        self.__link__(
            self.root(a)
            ,self.root(b)
            )

def main():
    n, m = map(int, input().split() ) 

    ins = [ list( map(int,input().split()) ) for i in range(m)  ]

    ds = dset(n)

    ans = [0]*m
    ans[-1] = n*(n-1)//2

    for i in range(m-1,0,-1):
        ans[i-1] = ans[i]
        a,b = ins[i]
        a -= 1
        b -= 1

#        print(ins[i])
       # print( (ds.root(a),ds.root(b)) ) 
        if not ds.same(a,b):
            x = ds.size(a)
            y = ds.size(b)
            #print((x,y))
            ans[i-1] -= x*y
            ds.unite(a,b)

    for e in ans:
        print(e)

if __name__ == '__main__':
    main()

C++

//cpp14

#include<iostream>
#include<vector>
#include<algorithm>

using sll = signed long long;
using ui =  long int;

class disjoint_set {
    std::vector<int> p;

    auto link(int x, int y) {
        //x = find(x);
        //y = find(y);

        if(x==y)
            return false;

        if ( size(x) < size(y) )
            std::swap(x, y);

        p[x] += p[y];
        p[y] = x;
        return true;
    }

public:
    disjoint_set():p{}{}

    disjoint_set(int size_) :
        p{}{
        p.resize(size_, int{-1});
    }

    auto find(int x)
        ->decltype(p)::value_type {
        
        return p[x] < 0 ? 
            x : p[x] = find(p[x]);
    }

    auto same(int x, int y) {
        return
            find(x) == find(y);
    }

    auto unite(int x, int y) {
        this->link(find(x), find(y));
    }

    auto size(int x)
        ->decltype(p)::value_type{
        return -p[ this->find(x) ];
    }

};

int main() {

    int n, m;
    std::cin >> n >> m;

    std::vector< std::pair<int, int> > vp;

    disjoint_set ds{ n };
    vp.resize(m);

    for (int i{}; i < m; ++i) {
        int a, b;
        std::cin >> a >> b;

        vp[i] = std::make_pair(a-1, b-1);
    }

    auto it = std::rbegin(vp);
    auto end_ = std::rend(vp);

    auto ansi = sll{ n } *(n - 1) / 2;

    std::vector<sll> ans{};
    ans.resize(m, 0);
    ans[m - 1] = ansi;

    for (int i{m-1}; i > 0; --i) {
        
        auto a = it->first;
        auto b = it->second;

        ans[i-1] = ans[i];
//     std::cout << " ab " << a+1 << " " << b+1 << "\n";
        if (!ds.same(a, b)) {
            auto x = ds.size(a);
            auto y = ds.size(b);

            ans[i-1] -= x * y;
            ds.unite(a, b);
//         std::cout <<" xy "<< x << " " << y << "\n";
            if (ans[i-1] < 0 )
                ans[i-1] = 0;
        }
        ++it;
    }

    for (auto&amp;e : ans)
        std::cout << e << "\n";
    
    return 0;
}    


感想

  • 最近色々だれてきていて、やる気がなくなってきていた
  • でもやればわかることが増えて、それは面白いので今後もやっていきたい
  • とりあえず現在の限界は、今回のD問題が解けないくらい

  • これ以上レートを上げていくということが、 このUnionFindを平然と実装できる人達と競争するということなら、現状の能力ではかなり厳しいと感じる

 
残念な失敗が多くて厳しい

  • DでWA->10^5^2はintでは扱えない
  • CでTLE->10^5^2は2secで全探索できない
  • Bは良くわからないがWAしたので別の書き方をしたら通った。良くない
  • UnionFindの実装に手間取るのが結構いたい

 UnionFindくらい難しいと何も考えずに書けるものでもない。
経路圧縮やランクによる効率化で処理が増えても複雑になっていくのを理解できるか、再帰を考えられるかが大事なんだろう。

 参考にしたUnionFindを書いた人は賢いやり方をしていてなかなかわからなかった。
今回はコンテスト後に復習をかねてもう一度、コンテスト時に用いた言語ではない言語で書きなおしたので、A-Dまでpython3とC++のコードがある。
 今更だけど載せるのってC以上の問題だけで良い気がする。
 書き方も色々試してみる。今回はmarkdown
コード載せる時に気づいたけどこれは正規の挙動なのか。
コード内でマークダウン効いたり改行でspan出たり何とかならないのかね。
<details>tagの中にdivを入れるのは、展開後の文章にmarkdownを適用するとき。

ABC118-ABCを解いた

 最近はうまくいかないこととか頑張り切れないことも多い中、
レートを維持どころか上昇させられたのは大変うれしい。
 Dはやっぱまだわからん。AからCまで自分なりの解き方を以下に示す。
 今回はすべてC++で解いた。

atcoder.jp


A - B +/- A

 A : 約数ってなんだっけ?から確認しなきゃならなかったけど、
サンプル見たらわかった。
 ある数を割り切る数のことらしいな。
 A問題は短く書いてしまいたいので条件演算子が有用。

 解き方:問題文の通りに実装すれば良い。

#include<iostream>
int main() {
	int a, b;
	std::cin >> a >> b;

	std::cout << (b%a == 0 ? a + b : b - a) << "\n";
	return 0;
}

B - Foods Loved by Everyone
 B : あんまり似たものはBで出ない気がする。
Kの意味が分からなくて何度か文章とサンプルを行き来した。
(入力のルールを見る限り、i番目の人間の回答の数、つまりi番目の入力の数らしい。)

 解き方:食べ物ごとに何回好きと言われたかカウントする。
N人全員が好きだと答えたのならば、ある食べ物が好きと言われた数はN回であるはずだ。
したがって、食べ物のカウントがちょうどNであるものの数を出力すれば良い。
 (他の実装では、boolかstd::bitsetあたりを使って一度でも選ばれなかったらfalseにして、   trueの数を数えるのもありかもしれない。)

#include<iostream>
#include<algorithm>

int main() {
	int n, m;
	std::cin >> n >> m;

	int food[30]{};  //[1,30] = [0,29] = [0,30)

	for (int i{}; i < n; ++i) {
		int k{};
		std::cin >> k;
		
		for (int ki{}; ki < k; ++ki) {
			int t;
			std::cin >> t;
			food[t-1]++;
		}
	}
	/*
	for (const auto&e : food) {
		std::cout << " " << e;
	}std::cout << "\n";
	*/
	auto ans = std::count(std::begin(food), std::end(food), n);

	std::cout << ans << "\n";

	return 0;
}

C - Monsters Battle Royale
 C : 難しかった 。
 ACはできたけれど証明して提出したわけではないので、
あまりいい回答ではないのかもしれない。
公約数だったかを求めるやつとか使えそうって思ったけど直接は使わない実装にした。
 結構ぐだぐだなので読み飛ばしてコードを見るほうがいいかもしれない。
 ちなみに今回載せる回答は提出後により良いコードに書き直したものだ。

解き方:
 まず入力される数列 Ai ( 1 <= i <= N ) の順序は
結果に影響を与えないっぽいのでソートしても良いだろう。
 次に Ax と Ay ( x != y かつ 0 < Ax < Ay とする ) の2体のモンスターが戦うことを考える。
愚直に問題文のとおりに書いてしまうとwhileで減算(a)とかになるだろうけど、
そこで得られる結果は %(b) の結果と同じだろう。(一応疑似コード載せるけどいらないかも。)

//疑似コード
//(a)
while( Ay > Ax )
  Ay -= Ax
  
//(b)
Ay %= Ax // 右のように書いてもいい==>  Ay = Ay % Ax


 他にも解くのに色々考えたのだけれど上手に説明できない。
ので列挙する。

     
  1. 数列 Ai に 1 が(戦闘が進んで一度でも)現れたら答えは1
  2. そうでないならAi %= Ax ( i != x ) を繰り返した0ではない最小値が答え
  3. ではどのようにAxを選ぶのが良いのか?
    (選び方によっては
    <i>結果が同じでも計算コストが増える
    <ii>結果が変わる
     ことはないのか?)
     
  • <i >A%Bした結果は[0, B-1]になるので、Bが小さい方が解も早く収束しそう?
  • <ii>については分からなかった。反例も思いつかなかった。
    上記2点が弱い部分で、可能であれば証明か反例どちらかを成し遂げて
    実装を決められたら良かったのだろう。

効率を考えて、除算する数は最小値 Aminにした。
以下コード。
 大事な部分?は余りが0の時は要素 *itk を 余りではなく最小値に書き換えているところ。
最小値に書き換えれば重複するので、次のuniqueで間違いなく消えてくれる。  
 余りが0の場合でも余りで書き換えてしまうと次のループで0除算が起きるようになって面倒。  
(コンテスト中の提出はこっちで通した)

#include<iostream>
#include<vector>
#include<algorithm>

int main() {

	int n;
	std::cin >> n;

	std::vector<int> v;
	v.resize(n);

	for (auto&e : v)
		std::cin >> e;
			

	std::sort(std::begin(v), std::end(v));

	auto it = std::unique(std::begin(v), std::end(v));

	while (std::distance(std::begin(v), it) != 1) {
		auto beg = std::begin(v);

		for (auto itk = ++std::begin(v); itk != it; ++itk) {
				*itk = *itk % *beg == 0 ?*beg : *itk % *beg;
		}
		std::sort(std::begin(v), it);
		it = std::unique(std::begin(v), it);
	}
	
	std::cout << v[0] << "\n";

	return 0;
}
    


( 何度もソートとuniqueするのは計算コスト的に如何なものか、
と実装しながら考えたが、まぁ一応ACする程度には早いらしい。
 「まぁ最初の数回は大きなコストを支払うかもしれないけれど、
最小値が小さくなるほど重複する要素が増えて、
この次に計算の対象になる要素数が大きく減るんじゃなかろうか」
みたいな感覚はあったけれど、実際どうなのだろう。)

みんなのプロコン2019-Cを解いた

atcoder.jp

 難しかったけど解けた。難しいのは300点ではなく400点問題だからだろうか。
普通C問って何点なんだろうか。
点数をあまり意識したことはなかったけど、点数から自分のACできるかもしれない問題かどうかが判断つくなら良いことだ。
Dもいけるかなって思ったけど解説見ても書けないので今はまだ無理なのだろう。


続きを読む

ゲームの話

私はゲームが好きなつもりだ。本当に好きなのか、よくわからなくなってしまったが。

 

 ゲームというものはきっと過程を楽しむことが目的なんだろうと思う。

知らない世界の知らないルールの下、どういうことをしていくと課題をクリアできるのか、を考えることは楽しい。当然現実へのリスクなんてものはないし、課題を自身で設定できるのもいいところなのだろう。

 

 それじゃあ最近はゲームをしている時間を、ゲームで設定した課題に取り組む時間を楽しめていますか、と自問自答すると即答できない。先行きの不透明さというか盲目的に取り組む熱意というか、そういうものがない気がする。

 

続きを読む

Unityのtutorial

 

Survival Shooter Tutorial - 2 of 10 : Player Character - Unity Official Tutorials (new) - YouTube

 

今やっているのはこの途中まで。
Unity version 2018 .3 .0f2 Personal

 

動画と環境が違うのが足を引っ張っている。今からでも変えたほうがいいかもしれない。

とりあえず「MinⅮrawer.csのMinAttributeがambiguousです」というエラーはこれで直る。同じ名前のクラスか何かがいろんなところに所属するようになったということだろう。

https://github.com/Unity-Technologies/PostProcessing/issues/725

 

 

別のエラーも出た。

「Can't add ~ The script needs to derive from MonoBehaviour! 」

このエラーは

1.スクリプトファイルの名前とその中で定義されているクラスの名前が異なる。

2.定義されているクラスがMonoBehaviouから派生していない。

(【Unity】Assets「DOTween」Can’t add script エラーが出る場合の対処法 - Qiitaとか)

あたりは検索すれば出る。けどこれらの方法では直らなかった。Assetのre-importもダメ。

 結局 playerに Add Component から new scriptでまっさらなPlayerMovement.csを作成して中身のほとんどをコピーしたらcompile Errorは無くなった(スクリプトの最初にあるusing等はnew scriptのものを優先したので”ほとんど”)。たしかこの解決方法は動画のコメントからctrl + f で見つけた気がする。Unity tutorialの動画のコメントは新しいユーザ向けにエラーの対処法が載っていたりする。

 Assetのupdateはしたつもりなのだけど、うまくいっているかはわからないのでそのあたりも原因の一つかもしれない。Assetって何から何まで含むのだろう。

 

 ちなみに動作を確認したところ、Animationの遷移がうまくいっていないように思う。Animator Controllerの細かいUIにもversion間で差異があるらしく、動画のものと見た目が違う。動画にはなかったEntryとExitに対して何かしなきゃいけないんだろう。今は時間がないので置いておくが、もう一度コメントや動画を見て確認する必要がある。コメントにいくつかAnimation遷移に関連したものがいくつかあったはずだ。

 

 *追記

 youtubeのコメントより:Animationの遷移はIDLEとMOVEの間の2つの遷移矢印それぞれを選択してから’Has Exit Time’パラメータのチェックを外すと直るようだ。ついでにカメラ設定も少し違うらしいのでMain CameraのtagをdefaultからMainCameraへ変更した。

 修正にどちらも必要だったのか、どちらかだけでよかったかはよくわからない。