- 投稿日:2019-01-27T05:05:21+09:00
大学生が有料スマホゲームを作った全てを公開するよ(3)ゲーム画面・インゲーム(前編)
大学生が有料スマホゲームを作った全てを公開するよ(2)開発環境とゲームの構成
の続き。今回は、ゲームシーンについて。
いわゆる、インゲームと呼ばれる部分。
逆に、メニュー画面とかはアウトゲームって呼ばれる。
pertica(この記事で紹介するゲーム)の場合は、パズルとアクションの仕組みを中心に解説していくよ。ここからはUnity
ここから先は、基本的にUnityの中での話になる。
プログラミングも出てくるし、
初めて見る人には結構難しいかもしれない。
出来る限り分かるように書いてみるけど、
よく分からない部分は飛ばしてもらって構わないよ。
なんとなーくでも理解してもらえると嬉しい。
後でUnityを使うようになったらまた見に来ても良いよ!Unityを知っている人や、ゲームを作った事がある人は参考にしてみて。
ボクはこうやってゲームを作ったよ。
この記事だと、後になるほど技術的になるから記事の下の方を見ると面白いかも。
これは前編で、後編はもっと技術的な話をするつもり。
アドバイスも待ってます!主要なゲームオブジェクト
前回の記事でも触れたけど、ゲームシーンは大きく3つの機能を持っている。
- メニューで選択したステージを生成する
- ステージが遊べる
- クリアしたら保存する
この3つだ。
そして、これを実現するために、
このゲームは10個の「部品」で出来ている。
- プレイヤー [Player]
- ブロック [Blocks]
- 敵 [Enemies]
- アイテム [Items]
- その他のステージオブジェクト [Others]
- カメラ [Main Camera]
- ゲームマネージャー [GameController]
- デコードオブジェクト [DecodeObject]
- UI(リトライボタン、戻るボタン) [UI Canvas]
- BGM・SE [BGMSource, SESource]
これらの「部品」を今後は、「ゲームオブジェクト」と呼ぶことにするよ。
Unityの中でも同じ名前で呼ばれるんだ。
でも長いから短く「オブジェクト」って言う時があるかも。ちなみに、[ ]の中はUnityの中での名前だ。
それぞれ説明するね。プレイヤー
ゲームを遊ぶ人が操作するキャラクターだ。
- タップした場所に弾を飛ばす
- 弾が当たったオブジェクトと入れ替わる
- 敵に触れたら死んでしまう
この3つの機能を持っているよ。
ブロック
基本的に四角くて動かないもの。
壁のこと。
マリオのブロックみたいな、四角くて動かないオブジェクトだ。敵
プレイヤーは敵に当たると死んでしまう。
プレイヤーに対して攻撃をしてくるオブジェクトだ。
マリオのクリボーとかノコノコと同じだ。アイテム
壁を出したり消したりするスイッチとか、ワープホールとか。
プレイヤーが道具として使うもの。
壁と違って動く可能性があって、四角くない。
マリオでいうと、キノコとかスターのことだ。その他のステージオブジェクト
今説明した「プレイヤー、ブロック、敵、アイテム」の事を、ボクは「ステージオブジェクト」と呼ぶことにしたんだ。
でも背景の星とか、敵が出すレーザーなんかはその4つでは分類できない。
だからブロック、敵、アイテムに分けにくいものを、例外として分類する為に「その他」を作ったんだ。
これでステージオブジェクトは「プレイヤー、ブロック、敵、アイテム、その他」の5種類になった。カメラ
カメラはスマホの画面にゲームを映す機能をもってる。
テレビはビデオカメラで撮影した映像を表示してるよね。
あれと同じで、ゲームの中の出来事を撮影してスマホの画面に表示してくれるんだ。
ズームしたり、カメラを複数使うことも出来るよ。ゲームマネージャー
ゲーム全体の管理をするオブジェクトだ。
このゲーム(pertica)では、
- リセット
- メニューに戻る
- ステージオブジェクトを作る
- フェードイン
の4つの機能を担当している。
デコードオブジェクト
ゲームの最初にステージを設置する機能を持ってる。
perticaはパズルゲームだから、パズルのステージを用意しないといけない。
ステージはたくさん欲しいよね。
そしたらボクだけじゃなくて、友達にもステージを作ってもらった方が良さそうだ。
友達がステージを作れるように、ボクは友達に「ステージを作るツール」を渡す。
友達はステージを作ったら、「保存」ボタンを押す。
そうすると、作ったステージが「どこに何があるか」を文字にしたデータになるんだ。
でもデータでは遊ぶ事は出来ない。
遊ぶ時にはデータを実際にステージに変換しなきゃいけない。
それをするのが、このデコードオブジェクトなんだ。
ゲームが始まった瞬間に、「メニューで選んだステージのデータ」から「ステージ」を作ってくれる、とっても賢いヤツなんだ。UI
リトライボタンとか、メニューに戻るボタンの事だ。
UIっていうのは、人がゲームを操作するために必要な仲介人なんだ。
「ゲームをもう一度やり直したい」とか、「メニューに戻りたい」と思うよね。
UIに頼めば、それを実際にやってくれるんだ。
頼む方法はボタンを押すだけだよ。BGM・SE
音を流す機能を持っている。
BGMはゲーム中にずっとなってる音楽
SEは「弾を飛ばす音」「スイッチを押す音」みたいな効果音の事だ。実装方法
ゲームが何で出来てるかは何となく分かったかな?
部品が「10個」なのは多いと思った?
それとも少ないと感じたかな。
段々と技術的な話に入っていくよ。
面白くなかったら飛ばして、最後のコメントだけ見ても良いよ。
今回は、ちょっとした制作秘話も書いてみた。ここからは、Unityを少し知っている人向けになると思う。
でもUnityを知らない人にも楽しんでもらいたいから、
簡単にUnityでのゲームの考え方を書いてみるね。Unityの簡単な説明
Unityでは主要なゲームオブジェクトで書いたような部品の事をゲームオブジェクトと呼ぶって言ったね。
それぞれのゲームオブジェクトは、さらにコンポーネントと呼ばれるもっと「小さな部品」で出来てるんだ。例えば、ゲーム内のプレイヤーは
- 今いる座標を定める部品 [Transform]
- 見た目を決める部品 [Particle System]
- 物理挙動を制御する部品 [Rigidbody 2D]
- 当たり判定を調べる部品 [Circle Collider 2D]
- アニメーションを制御する部品 [Animator]
- プレイヤーの動作(弾を飛ばす、移動するなど)を制御する部品 [Player(Script)]
みたいな小さな部品(コンポーネント)で出来ている。
このうち、上の5つはUnityが用意してくれてるんだ。
だから、ボクが作るのは一番下の一つだけ。
このゲーム特有の動きを決めるんだ。
脳を作ると言っても良い。
そして、この部品を作るためにプログラミングを使うんだよ。[ ]の中はUnityの中での呼び方で、Player(Script)はボクが作ったんだ。
こういうプログラミングで、自分で作ったコンポーネントを今後は「スクリプト」と呼ぶよ。
Unityの考え方では、
「車の部品にタイヤがあって、タイヤの部品にネジがある」みたいに
「ゲームの部品にプレイヤーがあって、プレイヤーの部品にPlayer(Script)」があるんだ。少し退屈かもしれないけど、このゲームのUnityでの実装の仕方を書き残しておくね。
プロジェクトツリー
メインシーンの説明の前に、Unityのプロジェクトのファイルツリーを書いておくよ。
Assets ├─ Animation ├─ Data ├─ Image ├─ Material ├─ Music ├─ ParticleSystem ├─ Prefab ├─ Scene └─ Script主要なのはこんな感じ。
さらにResourcesって名前のフォルダを必要に応じてそれぞれのフォルダの下に作るんだ。ゲームの仕組みを構造的に見る
ここからゲームシーンの説明だ。
Unity内のメインのゲームシーンは、DecodeStageと呼ばれるシーン名で管理している。
このシーンが実行されると、まずDecodeObjectがデータフォルダに入っているデータをもとにステージを生成する。
次に、Main Cameraがゴールからプレイヤーに向かって移動。
移動が完了すると、ゲームが始まる。
プレイヤーは、キャラクターを操作してゴールを目指す。
ゴールにたどり着くとリザルトシーンに遷移してこのシーンの役目は終わり。
分かりにくいかもしれないけど、上の図を目で追って見て欲しい。ここからは、10個のゲームオブジェクトについて実装の概要を説明するよ。
Unityの簡単な説明で書いたようにUnityは1つのゲームオブジェクトがたくさんのコンポーネントから出来ている。
前編の今回は、そのたくさんのコンポーネントのうちとても大事な「脳」の部分「スクリプト」を中心に解説するよ。
「スクリプト」はプログラミングを使ってボクが作ったものだったね。プレイヤー
プレイヤーの構造はこんな感じだ。
そしてプレイヤーの持つスクリプトはPlayer.csvoid IdlingAnimation() 踊るアニメーションを行うよ プレイヤーが一定時間何もしなかったときに使う。 public void Shift(GameObject obj) 弾が当たったものと位置を入れ替える 飛ばした弾が当たった時に使う public void Die() プレイヤーが死ぬ時に使う ・死ぬ時のエフェクトを表示 ・死んだ回数を保存 ・プレイヤーのゲームオブジェクトを非アクティブにする みたいな事をするよ public void Fire() タップした位置に弾を発射する 画面をタップした時に使う IEnumerator TypeAheadCoroutin() 先行入力を行う 弾のクールタイム中に画面がタップされた時に使う クールタイムの終了を待って弾を発射する準備をするこんな感じ。
「void IdlingAnimation()」とか「public void Shift(GameObject obj)」とかはその下に書いてある機能の名前だと思ってくれたら良い。
プログラミングを触ったことが無い人は、分かりにくいよね。
プログラミングを触ったことがある人にとっては、関数の事だ。
日本語で書いてある説明を、UnityではC#っていう言葉を使って書くんだよ。基本的に、先頭にpublicが書いてある関数は他のスクリプトから呼び出される。
書いていないのは、自身のUpdate関数から呼ばれる。
Update関数っていうのは、毎フレーム呼び出される関数のこと。
フレームっていうのは、ゲームの世界での時間みたいな感じだ。
フレームは1秒に60回くらい更新されるんだ。
だから、Updateも1秒に60回くらい呼び出される。
Updateの中でif文で条件を書いて関数を呼び出すんだ。ブロック
ブロックは次の3種類だ。
通常の壁 [Wall]
四角いオブジェクトでどんなものがぶつかっても動かない。
プレイヤーの放つ弾が壁に当たると、弾が消滅する。反射壁 [Reflection Wall]
四角いオブジェクトでどんなものがぶつかっても動かない。
プレイヤーの放つ弾が壁に当たると、弾が跳ね返る。スイッチ壁 [Switch Wall]
レーザーのような見た目をしている。
後で紹介するスイッチボタンによって、壁が出現している状態と消えている状態の2つが切り替わる。
壁が出現している時の機能は通常の壁と一緒で、弾が当たるとその弾は消滅する。
壁が消えている状態では、壁の機能は発揮せずに弾はそのまま通過するよ。さて、それぞれが持つスクリプトを説明するよ。
まずは通常の壁 [Wall]から。Wall.cs何もない。
実際は少しはあるんだけど、エフェクトを出すとか名前をつけるくらいの簡単なものだけだ。
通常の壁[Wall]はほとんどUnityの機能だけで出来ているんだ。
次は反射壁 [Reflection Wall]。ReflectionWall.csも、何も無い。
反射するはずの壁が、反射するスクリプトを持っていない。
実は、弾が反射するかどうかは壁ではなく、弾の方で処理しているんだ。じゃあ最後、スイッチ壁 [Switch Wall]
SwitchWall.cspublic override void SetStats(int[] stats) 初期状態で表示か非表示かを設定する。 public void SwitchEnabled() 壁の表示非表示を切り替える。 スイッチボタンの状態が切り替わった時に使う。スイッチ壁はスイッチボタンが押されたときに、状態を変える。
でも壁の状態を切り替える処理自体は、スイッチ壁の機能として用意しておくんだ。敵
今のところ敵は2種類。
名前はドッグ[Dog]とピッグ[Pig]だ。
名前の由来は...まぁそのうち話すよ...。
ドッグは直線移動して、壁に当たったら反対向きになる。
ピッグもそれは同じだけど、2つのピッグの間にはレーザーが出るんだ。
ドッグが持つスクリプトは、Dog.cspublic override void SetStats(int[] stats) 最初の状態を決定する。 上下左右とその間の斜め、8種類のうち最初に移動する方向を設定する。 void OnCollisionEnter2D(Collision2D coll) 他のゲームオブジェクトとぶつかった時に使う。 ぶつかったゲームオブジェクトがプレイヤーだったらプレイヤーを倒す。そして、ピッグはこれに加えて
Pig.csvoid SetLinePosition() レーザーの端っこの位置を決める。壁に当たったら向きを変える処理は書いていないんだ。
そういうのは全部Unityにやってもらってる。アイテム
アイテムは5種類。
- キューブ [Cube]
- クリアーホール [Clear Hole]
- スイッチボタン [Switch Wall Button]
- ワープホール [Dimension Crack]
- シフト弾 [Shift Bullet]
キューブはその場で静止しているだけのゲームオブジェクト。
クリアーホールはその位置にプレイヤーが到着すると、ゲームクリアー。
要するに、ゴールの事だ。
ワープホールは2つ1組で使う。
プレイヤーの飛ばす弾や敵が片方のワープホールに入るともう片方のワープホールに瞬間移動するんだ。
シフト弾はプレイヤーが発射する弾の事だよ。順番に見ていこう。
キューブ [Cube] のスクリプトは、
Cube.cs何もない。
これもUnityのコンポーネントだけで出来てる。クリアーホール [Clear Hole]が持つスクリプトは
ClearHole.csvoid OnTriggerEnter2D(Collider2D coll) データの保存、ゲームシーンの遷移をする。 プレイヤーが同じ位置に来た時に使う。クリアーした時のデータの保存もこのクリアーホールで行う。
保存専用のゲームオブジェクトがあるわけでは無いんだ。スイッチボタン[Switch Wall Button]は、さっき紹介したスイッチ壁の状態を切り替える事が出来るんだ。
スイッチボタンに敵のドッグかピッグがぶつかると、そのスイッチボタンと繋がってる壁の状態が変わる。
表示されてる壁は消えて、表示されてない壁が出現するんだ。スイッチボタンはこんな感じのスクリプトを持ってる。
SwitchWallButton.csvoid Switch() 繋がってるスイッチ壁の状態を切り替える。 敵がぶつかった時に使う。次はワープホール[Dimension Crack]
DimensionCrack.csvoid OnTriggerEnter2D(Collider2D coll) ステージオブジェクトがこのゲームオブジェクトに重なった時に使う。 void SetVisible(Vector3 viewpos) このゲームオブジェクトが現在カメラに写っているかの情報を更新する。 void SetPairDC() 現在このゲームオブジェクトと接続しているDimensionCrackを更新する。ワープホールは開発中は、DimensionCrack(次元の亀裂)って呼んでる。
クリアーホールもシーンをワープする感覚だから、ワープホールって名前だと分かりにくかったんだ。
今回の説明では逆に、次元の亀裂って言ってもイメージ出来ないと思ったからワープホールって書いたよ。最後はシフト弾[Shift Bullet]だ。
シフト弾はプレイヤーが発射するエネルギーの玉の事だ。
タップした方向に飛んでいって、「入れ替われるオブジェクト」にぶつかるとプレイヤーとぶつかったオブジェクトの位置を入れ替える。
スクリプトを見てみよう。ShiftBullet.csvoid OnCollisionEnter2D(Collision2D coll) 何かとぶつかった時に使われる。 ぶつかったオブジェクトが入れ替われるならプレイヤーの入れ替わる処理をする。 IEnumerator DieCoroutine() 弾が発射されてからしばらくたつと弾を消滅させる。 public override Die() 弾を消滅させる。 弾が反射壁以外のものとぶつかった時に使う。反射壁は反射する処理を書かないで、逆に壁に当たった時に弾が消える処理を書くことで実現してる。
反射する機能はUnityが用意してくれてるからボクはそれを上手に使うだけで良いんだ。その他のステージオブジェクト
「プレイヤー, ブロック, 敵, アイテム, その他」をステージオブジェクトと呼ぶって言ったね。
その他のステージオブジェクトには、背景の星とかピッグのレーザー[PigLazer]とか予測線[Prediction Line]がある。
他にもあるけれど、それはこのゲームシーンでは登場しないから今度話すね。
背景の星は、Unityのコンポーネントだけで出来てる。
ピッグのレーザーは、PigLazer.csvoid OnTriggerEnter2D(Collider2D coll) プレイヤーにぶつかった時にプレイヤーを倒す。これだけのスクリプトを持ってる。
予測線は、画面を長押ししたときに弾が飛ぶ軌道を赤い線で教えてくれる。PredictionLine.csvoid SetLine() 予測線の軌道を設定する。 予測線は、プレイヤーからタップした方向にに直進する。 予測線はものにあたるとその先までは貫通して表示されない。こんな感じのスクリプトだけで出来てる。
カメラ [Main Camera]
perticaでのカメラは基本的にプレイヤーが画面の中心になるように追う事だ。
でもステージの始めは、ゴールを映して徐々にプレイヤーにカメラを動かす演出がいる。
プレイヤーが入れ替わるときにも、少し遅らせ気味にプレイヤーを追うように演出する。
これを実装するために、ボクはこんな感じにいくつかの状態(ステート)で考えたよ。
持ってるスクリプトはMoveCamera.csvoid SetState(CameraState state) 状態(ステート)を切り替える関数 void LookClearHole_Move() ゴール(ClearHole)からプレイヤーの位置にカメラを移動させる。 ゲームの開始時に使う。 LookClearHoleという状態(ステート)のときに使われる。 void TracePlayer_Move() プレイヤーのカメラの中心に捉えるように、プレイヤーを追いかけ続ける。 プレイヤーが静止しているときに使う。 TracePlayerという状態(ステート)のときに使われる。 void GoToTarget_Move(GameObject objTarget, float speed) プレイヤーの方向にゆっくりと移動する。 プレイヤーが入れ替わるときに使う。 GoToTargetという状態(ステート)のときに使われる。実はカメラの制御は結構難しい。
良い演出が思いついても、難しくて実現できてないものもあるんだ。
特に、ズームとかが入ってくると厄介で...。ゲームマネージャー
ゲーム全体の管理をすると言ったね。
このオブジェクトはボクが作ったスクリプトだけで出来てる。Main.cspublic void Reset(float WaitTime = 1) ステージをリセットする。 ゲームシーンを最初から読み込み直す。 リトライボタンを押したり、死んでしまったときに使うよ。 public void LoadMenu() ゲームシーンを終了して、メニューシーンに移動する。 メニューボタンを押したときに使うよ。 public void FadeIn() ゲームシーンを終わるときに、画面全体を徐々に暗くするよ。 ゲームをクリアーしたり、メニューに戻ったりするときに使う。と、
Tutorial.csvoid Update() ゲームを初めてプレイする場合、チュートリアルを表示する。主にこの2つのスクリプトだ。
初めてスクリプトを2つもったゲームオブジェクトが出てきたね。
ゲームオブジェクトは2つどころか、スクリプトをいくつでも持てるんだ。ゲーム全体を管理すると言ったけど、
このゲームでは、ゲームマネージャーの仕事は少なめだね。デコードオブジェクト
Dataフォルダに入っている、StageCodeDataという名前のステージのデータを元に実際にステージを生成するよ。
データの仕組みや構造については別の回で説明するね。
デコードオブジェクトはスクリプトだけで出来ているよ。
ちなみに、これは先輩が作ってくれたんだ。DecodeStageScript.csvoid Awake() メニュー画面で選択したステージのデータをロードする。 データを解読(デコード)して、対応するゲームオブジェクトを次々に生成していく。 生成したオブジェクトによっては、初期状態を指定する。UI
UIは、ボタンの事だったね。
UIは基本的にスクリプトを持っていないんだ。
これは全部Unityが用意してくれているコンポーネントで出来ている。
ボタンを押すと、ゲームマネージャーが持ってるスクリプトのMain.cspublic void Reset(float WaitTime = 1)とか
Main.cspublic void LoadMenu()を使うように設定できるんだ。
BGM・SE
音を鳴らす機能を持っていたね。
これは、スクリプトを持っているよ。
まず、SEはSEPlayer.cspublic static void PlaySE(string keyname) 指定したSEを再生するよ。 流すSEは、事前に曲のファイルとキーワードを紐付ける必要がある。そして、BGMは
BGMPlayer.cspublic static void FadeinBGM(string keyname,float pertime=0.008f) 指定したBGMをフェードインしながら再生するよ。 曲が変わるときの違和感をなくすために、曲の入りで徐々に音を大きくするんだ。 流すBGMは、事前に曲のファイルとキーワードを紐付ける必要がある。 public static void FadeoutBGM(float pertime=0.009f) 指定したBGMをフェードアウトしながら終了するよ。 曲が変わるときの違和感をなくすために、曲の終わりで徐々に音を小さくするんだ。大事なのはこんな感じだ。
例えばプレイヤーが球を発射するときは音が鳴る。
この時、SEPlayerのPlaySEを使うんだよ。他にも、音を出したいときにはこのスクリプトを使って音を鳴らすんだ。
ちなみに、これも先輩が作ってくれたよ。まとめ
ゲームは「10個の部品」で出来ていた。
さらに「10個の部品」はもっと「小さな部品」で出来ている。
Unityではこの「10個の部品」を「ゲームオブジェクト」、
「小さな部品」を「コンポーネント」と呼ぶ。「コンポーネント」はUnityが用意してくれているものと、自分で作るものがある。
自分で作るコンポーネントの事を「スクリプト」と呼ぶことにしたね。「スクリプト」は大事な機能を持っていて「脳」みたいなものなんだ。
だから今回は、そんなスクリプトについて説明したよ。
実際には、パソコンが理解できるようにC#という言葉を使って書くんだ。
その言葉を書く作業がプログラミングだよ。なんとなく分かったかな?
制作秘話
perticaの制作を初めて2ヶ月くらいたったある日。
帰り道でボクが唐突に言った。「先輩、重力を消しましょう」
先輩「?????」
ボクが真顔で言うもんだから、ついに頭がおかしくなったかと思われた。
今のperticaは宇宙感のあるゲームで無重力だ。
でも昔は重力があったんだよ。昔のperticaは入れ替わった直後に重力で下に落ちたんだ。
でもそれだと、画面の動きが目まぐるしくて落ち着いて遊べなかった。
そこで、perticaでは重力をなくした方が良いんじゃないかと思ったんだ。先輩はオーケーしてくれて、それから「無重力計画」が始まった。
結構な変更だったから大変だったよ。
先輩もよく付き合ってくれた、本当に感謝してる。無重力にしたのが良かったのか悪かったのかは分からない。
でも面白いゲームを作るために思い切って変えたことに後悔はしていないよ。他にこんな話もある
メインシーンは「ゲームを作ろう」と思って最初に作り始めた部分だった。
「プレイヤー」「Dog」「通常の壁」「反射壁」「ClearHole」「ワープホール」「キューブ」「スイッチ壁」
これは機能だけなら最初の1ヶ月くらいで出来たんだ。
見た目とかは、適当な画像で代用してたけどね...。実は最初の頃は「Cat」とか「Ojisan」とかもいたんだよ。
その辺は今後「デザインについて」の記事も書くからその時に話そうかな。
多分その記事は神回になるよ。コメント
ここまで読んでくれてありがとう。
今回は、メインシーンについて触れた。
少し文量が多かったね...。Unityを知らない人にも、ゲームの仕組みが分かるように書いてみたんだけど、どうだったかな?
今このシリーズ記事の全体の構成をぼんやりと考えてるんだけど、多分全部で10回くらいになると思う。
なるべくこのゲームを全部公開したいんだけど、どうすると上手く伝えられるかな。
もし、気になる事とか書いてほしい事があったらコメントしてね。そろそろ実物があった方が分かりやすいんじゃないかな?
もし興味をもってくれたら、よろしくお願いします。
perticaは下のリンクからインストール出来るよ。大学生が有料スマホゲームを作った全てを公開するよ(1)イントロダクション
大学生が有料スマホゲームを作った全てを公開するよ(2)開発環境とゲームの構成
大学生が有料スマホゲームを作った全てを公開するよ(3)ゲーム画面・インゲーム(前編)
- 投稿日:2019-01-26T19:00:00+09:00
HPKIカードで署名してみる
はじめに
HPKIカードというカードがありまして、マイナンバーカードと似たようなものですが、それのお医者さん向けのものです。
手元にこのカードのサンプルがあったので中身を見てみたいと思います。
以前投稿した「マイナンバーカード検証」の続きみたいなものです。
- マイナンバーカード検証#1
- マイナンバーカード検証#2
- マイナンバーカード検証#3注意事項
- 日本医師会のサンプルカードを使いますが、専用のHPKIカードドライバを使うものではありません。
- HPKIカードのPINを何度か間違えるとロックがかかって使えなくなる(はず)です。
- ロックを解除する方法はここでは記載していないのでご注意ください。
- HPKIカードはいろんな種類があるみたいです。ここで書いてあることが全ての種類のHPKIカードで使えるかどうかはわかりません。
資料
目次
1.環境
2.ファイル構造
3.おやくそく
4.いろいろやってみる
4-1.証明書を取り出して公開鍵を取得
4-2.PINのリトライ回数を調べる
4-3.署名
4-4.Verify1.環境
- Windows10 1803
- .Net Framework 4.6.1
- Visual Studio 2017 C#
- パソリ-PaSoRi RC-S380
- HPKIカード
検証ツール
- OpenSSL
- Git Hub - Lib/DemoApp2.ファイル構造
HPKIカードのファイル構造はこの図のようになっており、認証用と署名用がある、という点ではJPKIと同じですが、APが二つに分かれている、という点で異なっています。
- 電子認証用AP
- 電子署名用AP
名前の通り解釈して、自分自身の認証に使うのが認証用AP、電子署名するときに使うのが署名用APということだと思います。
それで、両方にPrivate Keyがあるんで、どっちでも署名できる、PINはそれぞれ別に持っている、ということになります。3.おやくそく
基本的なことはJPKIカードと同じです。
- 証明書を取り出すためにPINは不要。
- Private KeyアクセスにはPINによるアンロックが必要。
- Private Keyにデータを投げつけて署名をGETすることができる。
- PINのリトライ回数は15回、PIN認証を間違えると減っていき、0になると・・・やってないのでどうなるかわかりません。
- PINのアンロックに成功するとリトライ回数は15に戻る。4.いろいろやってみる
認証用APでいろいろやってみます。
(署名用APもAPの識別子が違うだけで同じです)C#でICカードにアクセスする方法とか、APDUとは何ぞやという場合はこちらを参照⇒マイナンバーカード検証#1
4-1.証明書を取り出して公開鍵を取得
(1)APをSELECT
APDU =
0x00 A4 04 00 [10] [E8 28 BD 08 0F A0 00 00 03 91 00 00 00 00 00 02] 00
※[10]がデータサイズ(0x10⇒16byte)、[E8 28~]がAPの識別子。(2)認証用証明書をSELECT
APDU =
00 A4 02 0C [02] [00 16]
※[02]がデータサイズ(0x02⇒2byte)、[00 16]がFILEの識別子。(3)証明書をReadする
(2)でSELECTしたFILEのブロックから証明書をREADします。
証明書データはDER形式で格納されています。
バイナリデータの読み出し方法はこちらを参照⇒(3)READ BINARY証明書から公開鍵を取り出す
ここもJPKIと同じです。
⇒2.認証用証明書から公開鍵を取り出す取り出した公開鍵(バイナリデータ)はVerifyに使うので、ファイルにでも保存しておきます。
※DER形式の証明書を拡張子DERでファイル保存するとWindowsではダブルクリックで中身を見ることができます。見てみると「サブジェクトディレクトリ属性」にMedical Doctorと表示されます。
これで、ドクターです、と判断するのかもしれません。4-2.PINのリトライ回数を調べる
これから署名をやっていきますが、PINをつかうので、まずはPINのリトライ回数をチェックできるようにしておきます。PINを間違えすぎると大変なことになります。
(1)APをSELECT
さっきと一緒
(2)PINをSELECT
APDU =
00 A4 02 0C [02] [00 02]
(3)VERIFY PINコマンド
- APDU =
0x00, 0x20, 0x00, 0x88
- Response =
0x63, 0xCE
Responseで
0x63, 0xC?
といった値が返ってきます。このとき?
にロックまでのリトライ回数が返ってきます。このコマンドでリトライ回数が減ることはありませんので安心してください。4-3.署名
やっと署名をするところにきましたが、まず、HPKIカードで『署名』する機能とは一体何なのか、を説明したいと思います。
電子署名(でんししょめい)とは、電磁的記録(電子文書)に付与する、電子的な徴証であり、紙文書における印章やサイン(署名)に相当する役割をはたすものである
wikipediaより
例えば とあるPDFファイルの署名を得るには、PDFファイルをバイナリでReadしてHPKIカードに丸ごと送り付けるのか、というと、そうではありません。
HPKIカードで署名をするには
認証用鍵
FILEにSELECTして、APDUコマンドである形式のデータを送信します。そうすると、バイナリデータが返ってきます。それが署名です。
APDUで送信するある形式のデータは署名対象のファイルのことではなく署名対象のファイルのDigest Info
です。Digest Info
とはPKCS #1 (RFC 3447)
で決められている電子署名の形式です。つまり、送信側が署名対象のファイルから
Digest Info
形式のデータを作成して、そのデータをHPKIカードに送信するということです。具体的な方法はこちらを参照してください ⇒ 電子署名について
OpenSSLのコマンドでいうと(メモ)
openssl rsautl -sign -inkey hpkiprivatekey.pem -in digest_info_of_targetfile.dat
↑HPKIカードでやっていることopenssl dgst -sha1 -sign hpkiprivatekey.pem targetfile.dat
↑PKCS #1 (RFC 3447)の電子署名をOpenSSLで作成するコマンド4-4.Verify
署名検証方法はJPKIと同じです。
⇒4.検証5.ファイル構造を知る方法
HPKIカードはこのようなファイル構造をしていますが、マルチベンダーを想定していて、ある程度自由にファイル構造を定義できるような仕様となっています。ベンダー毎に構造が違っていては実装する側は大変ですので、ファイル構造の定義そのものをカードに格納する仕様になっています。
その仕様がJIS X 6320-15 (ISO/IEC 7816-15)
で、HPKIカードはこの仕様に基づいてファイル構造をEF.OD
というファイルに持っています。
EF.OD
を読み出してカード内のファイル構造を知ることができます。これは情報が少なくて、JISの資料をみてもわざとわからなくしているような書き方なんで全く理解できないのですが、こちらのサイトでわかりやすく書いてあります。
⇒Rubyを使ってHPKIカードのデータを読み取るおつかれさまでした
HPKIカードはJPKIカードとほぼ一緒なんで、あまり書くことがなかったなぁ
参考
- 投稿日:2019-01-26T16:23:06+09:00
【Unity(C#)】カメラに映った世界をGIF画像にする方法
この記事は『プログラミング完全未経験からUnityでの開発現場に迎え入れてもらえた世界一の幸せ者』
の記事です。そのつもりでお読みください。
GIF画像を作る
Unityで動画やGIF画像無しに説明するのしんどいので調べました。
ありました→https://github.com/Chman/Moments.git
ダウンロードして解凍してUnityのProjectにぶち込みます。
私がぶち込んだ際にはエラーが出ました。でも慌てないでください。
深呼吸してエラーメッセージを読みましょう。私のエラーは
MinAttribute
というクラス名を違う適当な名前に変更したら消えました。使い方を書きます。
使い方
①既存のScriptに追加
Recorder
というScriptを開きます。
Recorderクラスのフィールドにfloat returnBufferSize;
を追加し、
下記メソッドを追加します。public float ReturnBufferSize() { return returnBufferSize = m_BufferSize; }
m_BufferSize
というのは録画時間のことを指します。
次の工程で録画時間を他クラスに渡す必要があります。
そこで、録画時間を返すメソッドを用意しました。②新しいScriptを作る
便利なメソッドがたくさん用意されていたので助かりました。
ところどころよくわかりませんが動いたので良しとしましょう。using UnityEngine; using System; using Moments; using System.Collections; public class Rec : MonoBehaviour { bool isRec; bool isSave; int countTryRec; Recorder rec; Coroutine runCoroutine; void Start() { print("Rで録画開始"); rec = GetComponent<Recorder>(); rec.OnPreProcessingDone = OnProcessingDone; rec.OnFileSaveProgress = OnFileSaveProgress; rec.OnFileSaved = OnFileSaved; //BufferSizeが自然数でないとめんどうなのでエラーメッセージで忠告する if (rec.ReturnBufferSize() - (int)rec.ReturnBufferSize() != 0) { Debug.LogError("BufferSizeを自然数にしろ"); } } //保存開始時 void OnProcessingDone() { print("保存開始"); } //保存中 idという引数はよくわからん void OnFileSaveProgress(int id, float percent) { print("保存中:" + Math.Truncate(percent * 100.0) + "/" + "100%"); } //保存終了時 idという引数はよくわからん void OnFileSaved(int id, string filepath) { print("保存完了:" + "100" + "/" + "100%" + filepath); isSave = false; } void Update() { //録画 -> 録画中、セーブ中は押せない if (Input.GetKeyDown(KeyCode.R) && isRec == false && isSave ==false) { runCoroutine = StartCoroutine(RecCountDown()); } //保存 -> 録画中、セーブ中は押せない if (Input.GetKeyDown(KeyCode.S) && isRec == false) { rec.Save(); isSave = true; } } //録画開始~終了まで IEnumerator RecCountDown() { isRec = true; float second = 1f; int count = 0; //録画カウントダウン print("録画開始まで"); yield return new WaitForSeconds(second); print("3"); yield return new WaitForSeconds(second); print("2"); yield return new WaitForSeconds(second); print("1"); yield return new WaitForSeconds(second); print("開始"); rec.Record(); //残り秒数の表示 for (int i = 0; i < rec.ReturnBufferSize(); i++) { yield return new WaitForSeconds(second); count++; print("録画時間" + ":" + count + "/" + rec.ReturnBufferSize()+"秒"); } //録画終了 print("録画終了"); yield return new WaitForSeconds(second); isRec = false; print("保存するならS,上書き録画はもう一度R"); StopCoroutine(runCoroutine); } }③カメラに①、②をAdd Componentする
使いどころとしては以下3つくらいだと思います。
パラメーター名 機能 Frame Per Second 小さいとカクカク、大きいとヌルヌル Repeat 画像の繰り返し回数 Buffer Size 再生時間(録画時間) 実際に動かしてみた
キーボードのR
で録画のカウントダウンが開始します。
3秒後(正確には4秒後)に録画が開始されます。
録画開始後は録画終了までの秒数が表示されます。
もう一度録画をやり直したい場合はR
を、保存したい場合はS
を押します。
保存開始後は保存終了までのパーセンテージが表示されます。
保存完了後はファイルの保存場所が表示されます。
Editorウインドウを一度たたんで開くと保存されています。BufferSizeは自然数で
これのおかげでいろんな記事が書けるようになった気がします。
もっとこうした方がいいとか、他の簡単な方法とかあれば教えてください。
個人的にはシーンビューをGIFにできたら便利だなーと思いました。
- 投稿日:2019-01-26T16:17:34+09:00
Azure Function v2にHttpClientFactory+Pollyを組み込み、HttpClientの再試行を実装する
TL;DR.
- HttpClientの再試行はPollyライブラリを使用するのが一般的
- HttpClient(Factory)とPollyの統合は nugetパッケージ Microsoft.Extensions.Http.Polly で提供される
- Pollyが適用されたHttpClientをFunctionの入力にバインドする、Azure Function Extentionを作成した
- 実装例はこちら→GitHub
はじめに
こんにちは。Azure Function v2使ってますか。
最近ようやく.NET Core化しつつあるのですが、依存関係で躓くことが多いです。
.Net Coreとか、ASP.NET Core 2.xとか、.NET Standardとか、追いきれないし、
SDKが対応してなかったり、依存関係地獄。
今度はCore 3系と、ASP.NET Core 2.2とか?.NET 1.1と2.0しかない世界は平和だったと思う。
超図解 .NET Core エコシステムの全貌 的な本ないですかね。今回はAzure Function上でのHttpClientのはなしです。
System.Net.Http.HttpClient
HttpClientは簡単なようで扱い方が難しいクラスです。
具体的になにが難しいかは、昔からいろんな方がたくさん書かれているので、そちらに。
- .NETのHttpClientの取り扱いには要注意という話
- YOU'RE USING HTTPCLIENT WRONG AND IT IS DESTABILIZING YOUR SOFTWARE
- 開発者を苦しめる.NETのHttpClientのバグと紛らわしいドキュメント
System.Net.Http.IHttpClientFactory
そういったことを解消するため、
ASP.NET Core 2.1から、HttpClientFactoryが登場しました。
nugetパッケージ Microsoft.Extensions.Http で提供されています(つかいかたは だいたい↓のBLOG Part1,2,3,5読めばわかる。)
Microsoft.Extensions.DependencyInjection.Abstractions依存なので、対応するDIコンテナが必要です。
Polly
ざっくり、一時的な障害の対処系ライブラリ。
3大雑記の一つであるところの、芝村先生のしばやん雑記が詳しい。Polly+HttpClient(Factory)→Microsoft.Extensions.Http.Polly
Polly単体で使うのも悪くないですが、テストが…とかあるので、DIにしてくれるいいやつ。
HttpClient(Factory)とPollyの統合は nugetパッケージ Microsoft.Extensions.Http.Polly で提供される
(だいたい↓のBLOG Part4読めばわかる。)
Azure Function上のHttpClient
公式ドキュメントでは
static
で使いまわすことが推奨されています。Azure Function Extention
staticだとテストが…とか、staticだとDNSの問題が…とかあるので、
HttpClientFactoryを使いたい。再試行もPolly使いたいし。でも、Azure FunctionでDI使うのはとてもハードルが高い。
そこで、Functionの入力引数にHttpClientをバインドするExtentionを作ります。Azure Function Extentionと聞いて敷居が高い気もしますが、
入力バインドであればとてもシンプルです。たった3クラス数行。出来上がったものはこちら→GitHub
バインド用属性クラス
宣言だけ。
PollyHttpClientAttribute.csnamespace PollyHttpClient.Azure.WebJobs.Extensions.Bindings { /// <summary> /// Attribute used to bind to a <see cref="HttpClient"/> instance. /// </summary> [Binding] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)] public sealed class PollyHttpClientAttribute : Attribute { } }WebJobsStartupクラス
Azure Functionsランタイムが検出するExtentionのエントリポイントと、その処理
Extentionの追加と、HttpClientFactoryの登録をします。PollyHttpClientWebJobsStartup.cs[assembly: WebJobsStartup(typeof(PollyHttpClientWebJobsStartup))] namespace PollyHttpClient.Azure.WebJobs.Extensions { public class PollyHttpClientWebJobsStartup : IWebJobsStartup { public void Configure(IWebJobsBuilder builder) { //Extentionの登録 builder.AddExtension<PollyHttpClientExtensionConfigProvider>(); //HttpClient+Pollyの登録 builder.Services.AddHttpClient<PollyHttpClientExtensionConfigProvider>(nameof(PollyHttpClientExtensionConfigProvider)) .SetHandlerLifetime(System.Threading.Timeout.InfiniteTimeSpan) .AddPolicyHandler(HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == HttpStatusCode.NotFound || (int)msg.StatusCode == 429) .Or<TimeoutRejectedException>() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) )) .AddPolicyHandler(Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10))); builder.Services.Configure<HttpClientFactoryOptions>(nameof(PollyHttpClientExtensionConfigProvider), options => options.SuppressHandlerScope = true); } } }ExtensionConfigProvider
バインディングルールの設定
HttpClientFactoryをDIで受け取り。PollyHttpClientExtensionConfigProvider.csnamespace PollyHttpClient.Azure.WebJobs.Extensions.Config { [Extension("PollyHttpClient")] internal class PollyHttpClientExtensionConfigProvider : IExtensionConfigProvider { private readonly IHttpClientFactory httpClientFactory; public PollyHttpClientExtensionConfigProvider(IHttpClientFactory httpClientFactory) { this.httpClientFactory = httpClientFactory; } public void Initialize(ExtensionConfigContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // HttpClientFactory Bindings var bindingAttributeBindingRule = context.AddBindingRule<PollyHttpClientAttribute>(); bindingAttributeBindingRule.BindToInput<HttpClient>((httpClientFactoryAttribute) => { return httpClientFactory.CreateClient(nameof(PollyHttpClientExtensionConfigProvider)); }); } } }使い方
PollyHttpClient属性を付けた、HttpClient を引数に宣言。
Function1.cspublic static class Function1 { [FunctionName("Function1")] public static async Task Run( [TimerTrigger("0 */5 * * * *")]TimerInfo myTimer , [PollyHttpClient]HttpClient httpClient , ILogger log) { log.LogInformation("start func"); await SendRequest(httpClient, log, "https://httpstat.us/200"); await SendRequest(httpClient, log, "https://httpstat.us/500"); await SendRequest(httpClient, log, "https://httpstat.us/429"); //await SendRequest(httpClient, log, "https://httpstat.us/200?sleep=11000"); log.LogInformation("finish func"); } private static async Task SendRequest(HttpClient httpClient, ILogger log, string url) { try { using (HttpResponseMessage response = await httpClient.GetAsync(url)) { response.EnsureSuccessStatusCode(); var responseBody = await response.Content.ReadAsStringAsync(); } } catch (HttpRequestException ex) { log.LogError(ex, "HttpRequestException"); } catch (Exception ex) { log.LogError(ex, "Exception"); } } }ハマったポイント
ASP.NET Core 2.1のころ動作してて、このエントリを書こうとローカルで動かしてみたら、以下のエラーが
Microsoft.Azure.WebJobs.Host: Exception binding parameter 'httpClient'. Microsoft.Extensions.DependencyInjection.Abstractions: No service for type 'Microsoft.Extensions.Http.HttpMessageHandlerBuilder' has been registered.Azure Functions Runtimeが2.0.12265からASP.NET Core 2.2になっていて、
HttpClientFactoryが子スコープでHttpMessageHandlerBuilderを生成するようになった?ため?
らしい…こうしたら治った。けど、子スコープってなに…?Azure上では回し続けててエラーが出てなかった、Runtimeが1個前のまま動いてて、再起動したら無事死にました。
~2
とか設定した場合の、Runtimeの更新契機っていつでしょう…?
本番適用前だったので良かったですが、知らないうちにRuntime上がって死ぬとか悪夢
Runtime固定して、手動でアップデートするのがセオリーなんですかね…?皆さんどうしてますか?おわりに
今回作ったものは、以下が解決した場合、不要となるハズ。
- azure-functions-host issue#3737 Dependency Injection support for Functions
- azure-functions-host issue#3736 Built in support for HttpClientFactory
ここまで書いて、今更ですが、コンストラクタインジェクションができるようになったそうで。
バインディングじゃなくて、型付HttpClientをコンテナに入れるだけで良い気がする。
ふーむ。…確定申告しよっと。
参考
- 投稿日:2019-01-26T13:35:01+09:00
初学者が独習C#をざっくり読んでみる 第6回 -制御構文-
ぜんかいまでの記録
大体わかってきたから標準ライブラリに早くいきたい.
breakやcontinueとかいろいろのこってるらしいけれど飛ばして読みかけてしまった§4 制御構文(続
4.3 ループの制御
修了条件にかかわらずループを抜け出したい, 特定の周回だけをパスしたいときに使う構文
4.3.1 break命令
ループを強制的に中断させる.
break;
4.3.2 continue命令
現在の周回だけスキップして次のループに行く
continue:
どちらもifと一緒に使うのが一般的
break&continueint i; int sum = 0; for(i=1; i<=100; i++) { sum += i; if(sum > 1000) { break; } } Console.WriteLine(i); int sum2 = 0; for(int i=0; i<=100; i++) { if(i%2 != 0) { continue; } sum2+=i; } Console.WriteLine(i);4.3.3 ループのネストとbreak/continue
ネストした構文でbreakを使った場合内側のloopのみを抜けるよ.
40までの九九for (var i=1; i<10; i++) { for(var j=1; j<10; j++) { var result = i * j; if(result>40) { break; } Console.Write($"{result, 2}"); // resultを最低二桁で表示 } }4.4 制御命令のその他の話題
gotoとプリプロセッサ
4.4.1 goto
あぁ...スパゲティのおとぉ...
ラベルとgotoを使うことで強制的にコードのhそりを他の場所に移動ができます
goto//template labelName: goto labelName: //ex. goto THERE; Console.WriteLine("Skipped Line"); THERE: Console.WriteLine("DONE");以下注意点
- どこへでも移動できるわけではない. 他メソッドとかループ外から内部へ移動は不可
- 可読性低い. 使うか使わないかは宗教論争
使うときは
- ネストされた中のループから一気に外のループ含めて脱出するとき
- switchで他ラベルに飛ぶとき
等が一応は考えられる.
要するに一つの構文の中で完結しようなってことだと思う4.4.2 プリプロセッサ
コンパイラーに対する命令, ディレクティブを表すもの
ファイルの文頭に宣言をする
シンボルのスコープ(?)はファイル単位例を以下に
ディレクティブ 概要 #define A シンボルAを定義 #undef A シンボルAを未定義に #if A...#else...#endif シンボルAがある時, ifのコード, ない場合はelseのコードをコンパイル #if A...#elif B...#elif C...#endif ifのelse ifバージョン. コンパイラするコードのより複雑な制御 #region...#endregion コードブロックを指定 #warning [message] 警告メッセージを表示 #error [message] エラーメッセージを表示 #line n "file" エラー/ 警告時の行番号(n)/ファイル名(file)を固定 #pragma warning [disable/restore] list listで示された警告を有効(restore)か無効(disbale)にする 条件コンパイル
#defineと#ifを利用することでデバッグ時だけ有効なコードを定義できる
define&if#define DEBUG #if DEBUG Console.WriteLine("asdf"); #endifシンボルは特定の値を持つことはできない.
ifディレクティブの条件式
ifのなかでは, 論理演算子が利用可能
#if (DEBGUG && TESTED)
といった記述が可能折り畳み可能なブロック
#region [hogehoge
]を使うことでhogehogeというメモを残したままVS上でコードを折りたためる(メソッドができるように)
ようやく標準ライブラリだ...Chapterもついに5まで来た. 頑張ろう
- 投稿日:2019-01-26T05:06:48+09:00
Unity:GPU インスタンシング(Instancing) を WebGL がおかしい
はじめに
Unityの高速化の一般的な手法、GPU インスタンシングを使ってパフォーマンスを改善するようなデモを作成したので動作してみてください。 また、WebGLで出力した場合も有効かどうか確認し有効でしたので合わせてデモページもありますので各自の環境で試してみてください。
Link
- デモ : GPU Instancing無効
- デモ :GPU Instancing有効
- Github:https://github.com/fastsystem/unity-gpu-instancing-example
実装手順
こちらを参照してください
Unity:GPU インスタンシング(Instancing) を WebGL で実行する何がおかしいのか
- デモ
- 操作方法
- ホイールでドラッグ
- ホイールでズーム
- 右クリックでカメラの向き変更
初期表示が遅い
GPU Instancing無効:Unityロゴが表示後にすぐに表示される
GPU Instancing有効:Unityロゴが表示後に10秒くらいかかる。操作ができない
GPU Instancing無効:マウスでの操作ができる。
GPU Instancing有効:マウスでの操作が鈍すぎて実用に耐えない。FPSはでている
GPU Instancing無効:20fps くらい
GPU Instancing有効:50fps くらい若干の解決(2019/01/28追記)
「Quality」の設定からレベルを下げる事により本現象が若干和らいだので追記しておきます。
どちらがいいの?
あまり検証していないが、GPU Instancing を有効にしたオブジェクトを使いすぎると初期表示の遅さ、操作時の引っ掛かりがあり実用するのであれば無効も考えた方がよさそう。
バランスを見て有効・無効の割合をチューニングする必要がありそう。
- 投稿日:2019-01-26T00:47:59+09:00
全ての要素が条件を満たしているか?というLINQ
俺の拙い脳みそから記憶が揮発しないように、あえて書いています。
ブログにでも書け!というご意見は謹んでスルーします。すぐにLINQでハマるのが、僕の悪いクセ
List<string> strings の中から、"a"で始まる要素があるかを判断する場合。
strings.Any(str => str.StartWith("a"));これは知ってたから、すんなり解決できた。
でも、違う壁にぶちあったんだ。
Anyでできないことってあるの?
こういう存在の有無を確認する系は、Anyさえ使えればanything OK!だと思ってた。
次はすべて"a"で始まるか?の判断
あの時(といっても数日前)の俺は若かった。
strings.Any(str => !str.StartWith("a"));「いやいや!これじゃ"a"で始まらない要素がある証明だろ!」
というのはチェッカーの言葉。かなり声は荒めで。いやープッシュする前に自分でテストしてないんかい!って話なんですけど(笑
答えは教えてくれないんだ
仕方なく自分の席に戻り考えること数秒。
なんだ簡単じゃん!で提出したのがこれ。!strings.Any(str => str.StartWith("a"));無言で突き返された。
そして、マネージャーに呼び出された。理由は説明するまでもないよね。また、テストせずにプッシュしたんだ。
ちなみにこれだと"a"で始まる要素がないことの証明だね。そうだね、プロテインだね。あれ?Anyじゃできないの?
なんとか俺のしょうもない脳みそで絞り出した結果。
!strings.Any(str => !str.StartWith("a"));あれ、できたんじゃないか??
"a"で始まらない要素が無い == 全ての要素が"a"で始まっているということ。
狂喜乱舞してプルリク!ただの不勉強だったという結論
こう書くんだって。
strings.All(str => str.StartWith("a"));わかりやすい。。。圧倒的ッ!