【PS4】VA-11 Hall-A Cocktail Vending Machine の作り方
■VA-11 HAll-A というゲームについて
バーに訪れる客と会話しながら、カクテルを作ってお出しするバーチャルバーテンダーゲーム。カクテルの作り方は何種類かの材料を組み合わせることでできる。
■本題
カクテルは全25種類あり、ゲーム内で都度レシピを確認できるとはいえ、正直めんどくさい。そのレシピが完全に頭に入っていて、会話しながらよどみなくカクテルを作ることができるのがプロのバーテンダーかもしれないが、なかなかストーリーを進められないのがネックだった。
結論から言うと、レシピは毎回同じものを作っているので、自動販売機のようにカクテルのボタンを押すことで操作を自動化できないかということ。
で、出来上がったのがこれ。カクテルの要素は
アデルハイド A ブロンソンエキス B デルタパウダー D フラナガイド F カルモトリン K
氷ice 熟成Aged ミックスorブレンド Mix
の8要素から構成されていて、その組み合わせ次第。例えばバッドタッチは
A,B,C,D,F,K = 0,2,2,2,4 で氷あり、というように
カルモトリンはいわゆるアルコール分で、レシピによっては多く入れてもいいし、完全にゼロにしてノンアルコールカクテルを作ることもできる。あとは、材料の量を2倍にしてLサイズドリンクを作ることもできる。
■詳細
・実際の動作はps remote playでpcから操作した。ds4 emulater でキーボードの入力をコントローラーの各ボタンに割当。wasdキーが左スティックの各方向に対応している、といったように。注意点はps remote playはver5ぐらいの途中のバージョンを使用しないとならない。最新版では対策されていて機能しないらしい。そのため、リモートで操作することはできず、家庭内の同一ネットワーク上のPCからでしか接続できない。
・Cocktail Vending MachineのGUIとキーボード操作はpythonで作成。ktinterとpyautogui。試行錯誤の連続でステップバイステップでしか進められなかったが、なかなかそれも面白かった。最初は単に、引数をカクテルの8要素にしたCocktail関数を作り、そこから、25種類のカクテルに派生させた。ボタンを押して、そのカクテル関数が呼び出され、自動でカクテルをシェイクする。ポイントはカルモトリンの量を調整する、のチェックボックスとscale=倍数の入力ボックス。scaleはデフォルトでは1だが、2にすることで2倍量作れる。ゲーム上意味はないが整数ならどんな数字でも入れられる。
・チェックボックスにチェックを入れるとカルモトリンの量を聞いてくるので、0でもなんでも好きな数字を入れればよろしい。チェックを入れなければ、元々のレシピの規定量を入れる。ここが大変で、入力欄にしたとき空欄だとカルモトリンが0になってしまうし、カルモトリンを調整してもいいカクテルと規定量入れないと失敗するレシピがあって、デフォルトの数字を決められない。だからこういうやり方にした。
■終わりに
人はゲームに何を求めるのだろう。ゲームの楽しさとは一体何か。
自分なりにこの2つの問いに対する答えを考えると、ゲームの楽しさとは「困難さを克服すること、学習すること、成長することに対する快感」が一つ。「ストーリーを楽しむこと」が一つ。「キャラクターを愛でたり、グラフィックや音楽を味わう」ことが一つ。みたいな感じに考える。
「カクテルを作るゲームでその操作を自動化したら、それはゲーム性の放棄、破壊何じゃないの?」みたいな指摘が自分の中から生じたため、それに対する言い訳として言っている。さらに言えば、たとえその操作がなくなったとしてもストーリーを読み込むとか他の楽しみ方はあるわけで、カクテルづくりはむしろその弊害となっている節がある。
■ゲーム性の破壊?
何度もカクテルを作らされて、アデルハイド1杯入れなかったor入れすぎたせいで失敗して先に進めない(ゲーム下手すぎか?)みたいな事態を回避することができたわけだ。純粋にストーリーを楽しむにはこの要素無駄なんじゃない?いや、むしろこれが全体の世界観を作り出している、唯一無二の部分では?
あとは、このカクテル自動販売機マシーンは逆にゲーム性を理解していないと作れない。と思う。このゲームのバーテンダーの本質は【基本のレシピを作れる→客に合わせてカルモトリンやカクテルのサイズを調整できる】こと。調整機能がないとプレイしてても、客の要望に答えられず、苦労するのこの先どんなカクテルが要求されるのかわかってないとこうはできない。だからゲーム性の破壊などではない、としておく。ただし対戦要素のあるマルチゲーなどでは条件が不利になったりして公平ではないので、ツールの使用は推奨されない、という事かもしれない
脳の右側で描け
- 作者: ベティ・エドワーズ,野中邦子
- 出版社/メーカー: 河出書房新社
- 発売日: 2013/01/18
- メディア: 単行本(ソフトカバー)
- 購入: 1人 クリック: 8回
- この商品を含むブログ (5件) を見る
買いました。正直、これを一番最初に読むべきだったと思う。
この本が言っているのは最初から最後まで
「よく見て描け。見たものを見たまま描け」
ということ。ルーミスやhitokakuは素晴らしいお手本だけど、よく観察して、そのまま模写できる訓練を積んでないと、いくらやってもうまく描けない。先生のお手本が素晴らしくて心が折れそうになるといったことになる。以下、内容を軽く紹介する。
1. 前置き:描くことを楽しむ。
まず、この本が伝えたいことは技術とかより、絵を無心で描いている時間を楽しもうということ。普段使ってない脳の部分を使って描くことになるので、逆に普段の部分は休んでいるということ。リラックスして、休息の時間になる。
だから、描くこと自体が重要で上手い下手は重要ではない。一般的に写実的=美味い絵ということになるが、子供の絵なんて写実的じゃないが味がある、彼らは自然に絵を自分の心を表すツールとして使っている。家族と遊びに行ったとか、怖い思いをしたとか、そのままその時の心情を描いている。どうせ、見たまま正確に描いたって、その通りにならないんだし、それが君のオリジナルなんだ。
2.実際に描くための1+5つのステップ
とは言え、やっぱり描いた物を見たものに出来るだけ近づけたい。
そのためには5つのステップがある。…しかし、その前に大前提として
「見たまま描く」=「これはこういう形をしているはずだ、という先入観を捨てる」
ということが重要。これをRモードに脳を切りかえると読んでいる。
(∵)<これが顔に見えるように、脳は見慣れたものを簡略化して捉えていて、細部まで観察していない。うまく描くにはよく観察することだが、観察することはすなわち描くことなのかもしれない。
さて、実際のステップは
①エッジを捉える
②スペースを捉える
③相互関係を捉える
④光と影
の5つのステップから成る。自分はまだ2つ目のスペースを捉えるまでしか、理解できていないが、具体例を上げてみる。
エッジを捉えるというのは、俺は今足を描いているんだ、腕を描いているんだという意識を捨てて、ただ純粋に線を追うということ。こんな風に4分割したガイドの交点から交点まで、何度の角度で線を引く。ということを繰り返す。
そして、スペースつまりネガとポジという言葉のように隙間を意識することで、エッジを拾いやすくなる。具体的に言うと、組んだ足を描くと言うよりは、組んだ足の間を描こうと意識すると、うまくかける。
しかし、ここで悩みが幾つか。
①見たまま描くのもいいけど、見ないでは書けなくなってしまう。オリジナルが描きたいのに。
②ただの線を追っていくと、それはただの斜めの線であって、首の裏でつながる襟であったりとか、見えない部分を意識しなくなり立体感のない絵になってしまう。
114514年ぶりに絵を描いた
タイトル通りです。
久々に描いた割に、思ったより描けたと思う。うまくもなってないけど、下手にもなってない。模写だし。今回はこれに駄目出ししながら課題を見つけていこうと思う。
1.線に立体感がない
右の襟のあたりを見てもらえばわかるが、線を追っていってるだけなので、立体感がない。首の裏の見えないあたりとか全く意識してない。
このへんも全然ダメね。袖もダメ、肘から先と二の腕の前後感も出でない。
このへんもそう。おしりの下と椅子が設置している感じが見られません。
2.描き慣れてないものが下手
手な。下手すぎる。描き慣れてないのもあるけど、手は人が見慣れてるパーツの一つだから当然、評価も厳しくなる。少しでも変だとごまかせない。顔も同じ。
次に、良かった点。次もやっておきたい点。
足回り、スニーカーとかはよく観察して描けたと思う。特に組んだ足はあいだの空間の方を意識すると形を捉えやすかった。
今回は、キャンバスを模写する画像と同じサイズにした上で、縦横4分割して描き始めた。こうすると、”左膝が画面の半分あたり”とか、比率や線の書き始める点がわかりやすい。
以上
Processing でドット絵を描く
ドット絵とは?
・ピクセルが目視できるほど解像度が低い
・使われてる色が少ない
・ファミコン風、高解像度の格ゲーキャラドット等種類は様々
→既存の絵をドット絵風に変換することはできないか?
元絵
//ドット一つをオブジェクト一つとみなし、ColorDotクラスを作った。
//引数は左上の座標とドットサイズ。
//ドット内部の点の色を総計し、平均を出す。
//平均を出す際にはR,G,Bに一度分解しなければならない
PImage img;
int pallet_r = {
0,39,0,71,143,171,167,127,67,0,
0,0,27,117,0,35,131,191,231,219,
203,139,0,0,0,0,188,63,95,167,
247,255,255,255,243,131,79,88,0,255,
171,199,215,255,255,255,255,255,227,171,
179,159
};
int pallet_g = {
0,27,0,0,0,0,0,11,47,71,
81,63,63,117,115,59,0,0,0,43,
79,115,151,171,147,131,188,191,115,139,
123,119,119,155,191,211,223,248,235,255,
231,215,203,199,199,191,219,231,255,243,
255,255
};
int pallet_b = {
0,143,171,159,119,19,0,0,0,0,
0,23,95,117,239,239,243,191,91,0,
15,0,0,0,59,139,188,255,255,253,
255,183,99,59,63,19,75,152,219,255,
255,255,255,255,219,179,171,163,163,191,
207,243
};
final int dotSize = 3;
final int n = 100000;
Dot dots = new Dot[n];
void setup() {
String imgPath = selectInput();
img = loadImage(imgPath);
noStroke();
size(img.width,img.height);
background(255);
image(img,0, 0);
loadPixels();
for(int i = 0; i < width/dotSize; i++){
for(int j = 0; j < height/dotSize; j++){
dots[i+(j*width/dotSize)] = new Dot(i*dotSize,j*dotSize,dotSize);
}
}
}
void draw() {
for(int i = 0; i < width/dotSize; i++){
for(int j = 0; j < height/dotSize;j++){
dots[i+(j*width/dotSize)].draw();
}
}
}
void keyPressed() {
if (key == ENTER) { // Enterキーに反応
save("output.png");
}
}
class Dot{
int x,y;//ドット左上の点の座標
int dotSize;//ドットサイズ
float c_r = red(pixels[x+y*width]);
float c_g = green(pixels[x+y*width]);
float c_b = blue(pixels[x+y*width]);
color c;//ドットの平均の色
float []temp_color_distance = new float [52];
//コンストラクタ
Dot(int def_x, int def_y, int def_dotSize){
x = def_x;
y = def_y;
dotSize = def_dotSize;
//ドット内の色の平均
for(int i = 0; i < dotSize; i++){
for(int j = 0; j < dotSize; j++){
c_r += red(pixels[(i+x) + (y+j)*width]);
c_g += green(pixels[(i+x) +(y+j)*width]);
c_b += blue(pixels[(i+x) + (y+j)*width]);
}
}
c_r = c_r/(dotSize*dotSize);
c_g = c_g/(dotSize*dotSize);
c_b = c_b/(dotSize*dotSize);
//ドットの平均とパレット[i]との色の距離
for(int i = 0; i < 51; i++){
temp_color_distance[i] =
(c_r-pallet_r[i])*(c_r-pallet_r[i])+
(c_g-pallet_g[i])*(c_g-pallet_g[i])+
(c_b-pallet_b[i])*(c_b-pallet_b[i]);
}
//temp_color_distanceが最小になるカラーパレットを探す
float temp_color_distance_min = temp_color_distance[0];
for(int i = 0; i < 51; i++){
if(temp_color_distance_min > temp_color_distance[i]){
temp_color_distance_min = temp_color_distance[i];
}
}
//最小となる=一番近い色のカラーパレットで色を置き換える
for(int i = 0; i < 51; i++){
if(temp_color_distance[i] == temp_color_distance_min){
c_r = pallet_r[i];
c_g = pallet_g[i];
c_b = pallet_b[i];
}
}
}
//ドットを描くメソッド
void draw(){
c = color(int(c_r),int(c_g),int(c_b));
fill(c);
rect(x, y, dotSize, dotSize);
}
}