はてなキーワード: 転送とは
Youtubeはマクロとミクロのレベルで数多くのネットワーク状況に対する情報を提供してくれます。我々はそれらの情報も利用可能でゲーマーの体験を調整、拡大が可能です。
その通りです。StadiaをYoutubeとは異ならせる2つの事象があります。YouTubeはバッファ使用が可能でリアルタイムでもなく、ゲームのようにインタラクティブでもありません。Stadiaはバッファリングができません。Stadiaはフレーム数が絶対的に正確である必要があります。
次に2つ目ですが、ユーザがDoomやAssasin's Creedレベルの画質のゲームを遊ぶ場合、その期待はYouTube向けにユーザが作成したコンテンツよりもずっと高くなります。
さらにもう1つお話する価値がある点として、YouTubeとStadiaの繋がりについて今後も多くのことを話し、デモをしていきますが、大前提としてゲームとはリンクであり、リンクは無数の方法で配布、探索、共有が可能であることが挙げられます。
その通り、全く仰る通りです。あなたが仰った通りのシナリオになります。また例えばEurogamerが新しいゲームをレビューしているとしたら、ユーザはレビュー記事をクリックするだけでダウンロードすることもなく、パッチを当てることもなく、インストールすることもなしに簡単にそのゲームを試すことが可能です。また他にはあなたの記事が新しいマップやキャラクター、レベルについて記述している場合でもそうです。我々にはState Share(状態共有)と呼ばれる既存の技術と一緒に働くとても強力な新技術を用意しています。
State Shareは本当にとても強力な機能でユーザが最新のバージョンのゲームを遊んでいる時に、同時に友人や私、または世界中にそのゲームをストリーミング可能です。誰が相手でも問題ありません。またユーザがゲーム内での最新の武器、例えばDoomのFlaming Swordや、とにかくそのゲームで最高のアイテムを入手したとします。そして私が「カッコイイ! 私もそのFlaming Swordが欲しい!」と考えたとしましょう。私がそのビデオをクリックすればそのメタデータがそれら全ての属性を直接私のゲームにも転送します。だから誰でもが他人のビデオストリームに飛び込めるだけでなく、その他人の状況全てと共にゲームに飛び込めるのです。これは開発者とゲームが設定可能です。これについては他にも例がありますが、1つはユーザがゲームの特定の難しい状況に陥った場合に、ユーザはコミュニティにその状況を解決できるか試してもらうことが可能です。全く同じ状況で渡すことができます。
我々がYoutubeとの接続で行っている別の例は、典型的なシナリオとして、私がパズルベースのアドベンチャーゲームをやっている場合によくある状況があります。そのゲームのあるポイントで立ち往生してしまいました。その特定のパズル、例えばトゥームレーダーのある墓や何かができないとしましょう。現状では電話を取り上げるかラップトップに向かいヒントをダウンロードするでしょう。それかYoutubeに向かって動画を探します。しかしStadiaではボタンを押して「Hey, Stadia. このボスはどうやってやっつければ良いんだい?」と聞きます。するとStadiaがYoutubeの動画を探してきてゲーム内で再生します。私はレベルの解答を見て続けることができます。
先程、申しましたゲームとはリンクであるとの点ですが、任意のWebサイトはリンクを持つことができます。Discord、Facebook、Twitter、電子メール、テキストメッセージ、WhatsApp、Googleの検索結果、さらにはGoogle Play Storeですらゲームの配布が可能になります。
リンクです。
そうです。インターネット全体に渡り分散された広大な領域の利点を得ています。我々は開発者とパブリッシャに対し、インターネットがストレージであるとのコンセプトを広めています。もちろんStadiaは専用のストレージを持っています。しかし我々は開発者に対しゲームを自社のコミュニティへ持っていくことも、例えどこにコミュニティがあろうとも許可します。このことが配布と探索を開発者にとってとても良い意味で逆さにします。また我々にはとても高速なマーケティング技術がありますので、パブリッシャは体験をできる限り多くの人々にとても効率の良い手段で配布することが可能です。
コミュニティに対するモデレーションにはとても強固なアプローチを取ります。YouTubeは途方もない投資をこの領域に行ってきました。我々はそこに提携をし、さらに家庭レベルでも行います。我々はこの業界でも最も優れたペアレンタルコントロールとゲーマーの健康のコントロールを提供します。両親は子供が何を遊ぶか、誰と遊ぶか、いつ遊ぶかを管理可能です。
我々は全てをユーザの制御下に置くことを使命としています。Googleが提供する制御と機能と同じレベルのものがStadiaでも提供されるとお考え下さい。
それは私共が答えることではありません。Stadiaはゲームの新しい開発の仕方、ゲームの新しい探し方、ゲームの新しい楽しみ方を提供します。我々には大きな未来への志があります。大規模に発展したいと考えています。それは一晩で起こることではありません。また我々はこれまでに存在し、我々をここまで導いて下さった物、全てを尊敬しています。私達は業界全体が成功し、成長することを望んでいます。
やっぱり途中で切れたので続きから
違います。
そうです。
はい。Stadia Games and Entertainmentの組織を発表しました。これは我々の1stパーティのスタジオです。
はい。
Googleは開発者に対し全てのツールを支援しています。Stadia向けの開発は彼らにとって別のターゲットにしか過ぎません。Visual Studioを用いる既存のツールや彼らが用いるツールの全てと共に、彼らのワークフローに統合されます。従ってStadia向けの開発はPlayStationやXbox向けの開発と同じくらい簡単です。
我々はUnrealもサポートします。UnityがStadiaをサポートします。予想される多種多用な業界標準のツールとミドルウェアが準備されます。
とても良い質問です。我々はユーザに対し彼等のインフラの中で何が起こっているかをできる限り理解できるよう支援する必要があります。また我々はゲーマーに対し最適な体験を得られるようなチューニングを行うことが可能な情報に対し投資を行うだけでなく、我々自身の技術を用いて最良のパフォーマンスを実現するつもりです。Googleの技術の多くがインターネット網の基盤であることを思い出して下さい。我々はDCからの情報がどのようにユーザに届くかを良く理解しております。できる限りの最適化を行うつもりです。
その通りです。それこそが我々のプラットフォームの根本的な差別化ポイントです。既存のゲームカタログを持つデベロッパにとってStadiaは簡単で親しみ易いものです。我々はできる限り摩擦なくゲームの移植を行えるようにします。なぜならゲーマーは好きなゲームを遊びたいですし、彼らの愛するキャラクター、ストーリー、世界を楽しみたいのです。しかし我々はまた開発者に未来を描く新しいキャンバスをも提供します。ゲームを高速に配布し、プレーヤーと新しい手段で、特にYoutubeにて繋げます。そして開発者が持つアイデアを実現するための前例の無い技術を提供します。
解決されたと同時に緩和されています。まずデータセンターに対しより多くの人々がより良い経験を得られるようにするための投資が行われました。また圧縮アルゴリズムについては我々に抜本的な先進性が存在します。Googleは圧縮アルゴリズム標準仕様の先駆者でありこの点がストリーミングの将来をより確実にします。残念なことですがGoogleでも制御できない点が光の速さです。そのためこの点が常に要因となります。しかし常に理解しなければならないこととして、我々は常にエッジ(終端)にもインフラを構築していることが挙げられます。Googleの中心にある巨大なデータセンタだけではありません。我々はできる限りエンドユーザの側にインフラを構築しています。それによって歴史上の幾つかの問題は回避することが可能です。さらにまだ率直な、あまり洗練されていないProject Streamのストリーマーでも信じられない結果を出しています。さらに我々はサービスリリース時に1080p60を超える品質を実現できるだけの根本的な改善を行いました。我々は8Kに至るでしょう。
圧縮にネットワークです。我々はGoogleがインフラに投入した数々の改善点に依っています。BBR、QUIC、WebRTCを基盤としてその上に構築がなされました。だからIPパケットの低レイテンシでの配信だけでなく、送信元へのフィードバックも行うことが可能です。ですのであなたが仰るZenimaxが使用した技術なら、彼らはここでも利用することが可能です。彼らは彼らのゲームの最適化を行うことができるでしょう。我々はフレーム毎のレイテンシを予測が可能で彼らにそれに合わせて調整を行わせることができます。
我々は改善を続けます。Streamは最初のバージョンです。我々は性能向上のために調査を行っており、レイテンシに適応していきます。リリース時にはより良くなっているでしょう。
確かにそのとおりです。そしてそれこそがGoogleが何年もかけて開発してきたスキルであり、抜本的なスケールする能力です。我々がどうやって実現しているのか、何をしてきたかについては今日は詳細にはお話ししません。しかしGMailやMap、Youtubeが同時に利用可能であるためと同じ基本的な技術のいくつかが我々が依るものです。
我々は競合他社が何をしているかは存じておりません。
我々の第一世代システムに導入されるGPUは10Tflops以上の性能があり、さらにスケールアップします
AMDです。
情報を公開したくない訳ではないのですが、このプラットフォームが進化することのほうがより重要です。そしてこの進化がユーザと開発者の双方に対しシームレスに行われることを確認して頂きたいのです。そして進化は常に継続し、誰もが常に最新で最高の物を手に入れます。
開発者にもこのように考えて欲しいのです。もちろん完全には抽象化されていません。特にゲームの開発者にとっては。しかし我々はそれでもこのプラットフォームが常に進化していると考えて欲しいのです。速さや容量、リソースには制限されていないのだと。
シェーダコンパイラのツールをいくつか開発しました。これらは開発を楽にするでしょう。しかし現在のGPUはとても優れており開発者が既にVulkanに親しんでいれば、例えばid Softwareさんは既に全てVulkanに移行していますが、そのような開発者の方々には既存のゲームをStadiaに移植するのはとても簡単です。Doom Eternalが4K、60フレームで動いでいるのは既にご覧になったと思います。非常に素晴しい状態です。これこそが我々にとって重要な証明ポイントです。FPSはグラフィックとプレイアビリティの双方で要求が高いゲームです。 従ってこれは我々のプラットフォームの強力な証拠であり、idさんにも講演して頂きます。
x86で2.7GHzで動作しています。開発者にとり慣れのあるものです。開発全体を通して、CPUは制約となる要因ではありません。我々は全てのタイトルを動作するに十分なCPUを提供します。
沢山です。
しかしサーバ級のCPUです。Stadiaはこれまでのコンソールと違いパッケージングに制約を受けません。熱対策の問題も異なります。コンソールとはサイズやパッケージングの動機が異なります。データセンタの中でそれはとても汚なく見えるかもしれません。一方でとても帯域幅が高いメモリが使用可能で、とても高速なペタバイト級のローカルストレージも使用可能です。ご家庭のコンシューマデバイスよりも数百倍は速い物です。
パートナーには彼らが話せる時点で彼らの計画を教えてくれるよう伝えています。Stadiaをこの世界で最も偉大なゲームの開発者達に説明することにはとても興奮します。Stadiaは、開発コードではYetiと呼ばれていましたが、Stadiaのビジョンを説明すると、開発者のリアクションは「これは私が期待したものそのものだ。これはまさに我々の次のゲームのためのビジョンそのものだ。elastic computingの考え、次世代レベルのマルチプレーヤー環境、ゲームを観ることと遊ぶことの境をあやふやにし1つの体験にすること」と話されます。
イノベーションの1つの領域として、最初のほうで述べましたが、マルチプレーヤー環境において、単純にパケットを複数のプレーヤーにリダイレクすることから、原子時計レベルでのコンシステンシーを全ての状態遷移において定期的にクライアント間で更新する真のシミュレーションへの移行が挙げられます。これにより開発者はこれまでには不可能だった分散された物理シミュレーションを得ることができます。これだけでもゲーム設計のイノベーションに対し大きく寄与します。このため多くの開発者が、大袈裟でなしに、実際にとても感動的なリアクションを我々のプレゼンに対して返して下さっています。
これこそがゲーム業界の素晴しい点です。技術が常に創造性を刺激し、ゲームに対しより大きな聴衆を作り、そのことがプレーヤーと開発者に対しより大きな機会を作ってきました。エコシステムが進化し、正の方向に回り続けるなら、それはゲームを遊ぶことにとって良いことです。
3台のGPSが一緒に実行されるデモを行っています。私は上限が無いとは申しません。しかし我々は技術上の限界を上げています。そしてStadiaは静的なプラットフォームではありません。このプラットフォームは5年や6年の間、レベルが変わらない訳ではありません。開発者とプレーヤーの要求に従い、成長し、進化するプラットフォームです。なぜならStadiaはデータセンタの中に構築されています。進化させるのは我々にとって簡単なことです。
CPU/GPU/メモリ帯域幅の変更にはいくつかの自然な段階があります。これは家庭の物理な小売の端末よりももっとスムースでより継続的な進化です。しかし、より重要なことは基盤データセンタ網とそれに含まれるネットワーク技術への投資です。この2つが一致して行われることが重要でどちらか1つではダメなのです。
それは我々も既に行っています。Googleが既に20年以上、行っていることです。我々が依って立つまた別の巨人の肩です。
我々はStreamをさらに強化させています。従ってユーザはこの制約が全体のスタックに対する改善と最適化、また特に時間によって緩和されることを期待するでしょう。我々はその期待の上を行きます。
私は具体的な数値についてはコメントしません。しかし当然低くなります。
インターネットの接続環境はStadiaをリリースする市場では全体的に上昇機運が見られます。つまりこのパフォーマンス特性はますます多くのユーザが利用可能になります。
さらに繰り返しになりますが、BBRを初めとする我々の技術があります。さらに覚えておいて頂きたいのは我々のネットワークに対する理解はそのままではありません。それらもまた時と共に改善されていきます。Youtubeはマクロと
Eurogamerにより独占配信されたStadia開発者二人に対するインタビュー記事。
---
タイミングの問題です。20年間の蓄積によりGoogleにはデータセンタ内のパフォーマンスに優位性が存在します。Googleはデータセンタ内ではHWメーカーです。我々はデータセンタ内で何年もの間、高い性能で端末間を接続する基盤を構築してきました。Youtubeでの経験からプレーヤーサイドの観点からだけでなくデータセンタ内部からの技術的観点からの技術統合を行ってきました。他社でもその視点は存在していますがGoogleにはその点に固有のアドバンテージが存在します。
その通りです。我々にはレガシーがありません。全てが21世紀のために設計されています。開発者は制限の無い計算資源が利用でき、何よりもマルチプレーヤーをサポートできます。これまでのマルチプレーヤー環境は一番遅い通信に影響を受け開発者は最も遅い接続に対し最適化が必要でした。我々のプラットフォームではクライアントもサーバも同じアーキテクチャの下にあります。これまではクライアントとサーバの間のpingに支配されていましたが我々の環境なら最速でマイクロ秒で済みます。だからプレーヤーの数は単一のインスタンスにて動的にスケールアップが可能です。バトルロイヤルなら数百から数千、数万のプレーヤーが集まることも可能です。それが実際に楽しいかどうかは置いておくとしても、新聞のヘッドラインを飾ることが可能な技術です。
両方です。
ユーザが我々のプライベートLANからはみ出さないだけでもその効果は大きいものです。Googleは45万kmに及ぶ光ケーブルにより世界中のデータセンタ間を接続しています。米国の西海岸から東海岸まででも20ms、フランクフルトからマドリッドでも20ms。これにより開発者は最も極端な場合においてもレイテンシが予測可能でそれに従い設計を行うことができます。
StadiaはYoutubeの技術と深く結びついていますが、実際には一歩引いています。今日のゲーム業界を考えてみて下さい。2つの世界が共存しています。1つはゲームをプレイする人々で、もう1つはゲームを見る人達です。2億人の人々がYoutubeでゲームを毎日見ています。2018年には述べで500億時間がゲームを視聴するのに費されています。時間と人口の双方で信じられない程の視聴が存在します。我々のビジョンはこの2つの世界を1つにすることでゲームを見ることができ、かつ、プレイもできる、双方向に楽しめることです。
つまり重要なのはゲームシステムでもなくコンソールでもありません。噂とは異なり我々はコンソールビジネスには参入しません。我々のプラットフォームの要点はコンソールでは無いことで、皆が集まる場所を作ることです。我々は箱でなく場所を作る。今までと異なる体験を得られる場所です。ゲームを見るなり、遊ぶなり、参加する場所であり、かつユーザが楽しむ場所であり、ユーザが他人を楽しませる場所です。
だから我々のブランドはStadiaといいます。これはスタジアムの複数形です。スタジアムはスポーツを行う場所ですが同時に誰もがエンターテイメントを楽しむ場所でもあります。だから我々はそれをブランドにしたかったのです。皆が遊んで、観て、参加して、さらにはゲームをする場所。一歩下がって見ることもできる場所。常にどのボタンを押したかを意識しないでも良い場所。他のアーキテクチャでは実現できない場所です。
その通りです。そして単純に技術的に深い点を求めて、我々は第一世代でも4K60fps、HDRとサラウンドをサポートしました。さらに開発者が必要なインフラに従ってスケールします。それだけでなく、同時にYoutubeに常に4K60fpsHDRで画像を送信することが可能です。だからあなたのゲーム体験の思い出は常に最高の状態になります。
プレーヤー次第です。Googleは全てを記録はしません。もしプレーヤーが望むならGoogleは4Kでストリームします
共有が友達だけか、世界中に公開かも自由に選択可能です。Googleはユーザに制御を明け渡します。もしユーザがYoutubeで公開すれば誰でもリンクをクリックすることでそのゲームを遊ぶことができます。
そう。そしてこれはマルチプレーヤーゲームのロビーの新しい形となります。Youtubeのクリエイターなら誰でもがファンやチャンネルのsubscriberを自分のゲームへと誘うことができます。生主として、Youtubeのクリエイターとして私は視聴者を私のゲームに瞬間的に招待できます。それが私と10人の友達でも、(訳注: セレブの)Matpatと彼の数百万の購読者でも、技術は同じです。
Googleアカウントの一部です。従ってGMailアカウントがStadiaへのログインに利用できます。他の基盤についても説明させて下さい。最初のサービス立ち上げから全ての画面への対応を行います。TV、PC、ラップトップ、タブレットに携帯です。我々のプラットフォームの基本は画面に依存しないことです。これまで40年間、ゲーム開発は端末依存でした。開発者として私は制約の範囲内で、私の創造性を開発対象の端末に合わせてスケールダウンする必要がありました。
我々はStadiaでそれを逆にしたいのです。我々は開発者に対し彼らの考えをスケールさせ、どの端末の縛りからも解放したいのです。パフォーマンスに優れ、リンクをクリックすればゲームは5秒以内に開始されます。ダウンロードもなく、パッチもなく、インストールも必要なく、アップデートもありません。多くの場合、専用のHWも必要がありません。従って古いラップトップでChromeブラウザを使用する場合にでも皆さんが既に持っているだろうHID仕様に準ずるUSBコントローラが動作します。そして、もちろん、我々自身のコントローラも開発中です。
コントローラを自作する理由にはいくつかあります。1つはTVへの接続です。我々はChromecastをストリーミング技術に採用します。Stadiaコントローラの最も優れた機能の1つはそれがWiFi接続でDC内のゲームに直接接続することです。ローカルのデバイスとは接続しません。
その通りです。これこそが我々のブランドの実現であり、具現化です。そして独自コントローラにより最高のパフォーマンスが実現します。ゲームに直接接続するためにプレーヤーは画面を移動することが可能です。プレーヤーはどの画面でも自由に遊び、停止し、他の画面でゲームに復帰することが可能です。
そしてコントローラには2つの追加されたボタンがあります。1つはGoogle Assistantの技術とマイクを用います。ユーザの選択により、ユーザはプラットフォームとゲームの双方に対し、自然言語を用いて会話が可能です。例えば「Hey, Google。MadjとPatrickと一緒にGame Xをやりたいな」と言えばStadiaがマルチプレーヤーゲームを指定した友人と共に直ぐに開始します。
我々はゲーマーを可能な限り素早くゲームに辿り着かせるよう考えています。数多くの研究を行いましたが、多くのゲーマーがゲームを起動したら直ぐに友人とゲームを開始したいと考えています。ゲーマーはUIに時間を費したくは無いのです。
誰かが言ったことですが、現在のコンソールは起動した時にまるで仕事のように感じると言うのです。ゲーム機自体の更新や、ゲームの更新があります。我々はそれらを完全に取り除きたいと考えています。もう1つのボタンは、ちょっと趣が異なるのですが、Youtubeにシェアできます。
Youtubeが観られるならどこでもStadiaは動きます。
Chromecastはスマホからストリームを受取はしません。Chromecastはスマホから命令のみ受けます。画像はNetflixやYoutubeから直接受け取ります。Stadiaの場合、StadiaコントローラからChromecastへとこのゲームのインスタンスへと接続せよと命令がなされ、Chromecastはゲームインスタンスから動画のストリームを受け取ります。クライアントはとてもシンプルです。行うのはネットワーク接続、ビデオと音声のデコードのみです。Chromecastは入力を処理しません。全て入力はコントローラが扱います。ビデオと音声とネットワーク接続はChromecastの基本動作で全て既に組込まれています。
そうです。とても良く出来ています。WiFiに繋ぐだけです。コントローラにはWiFiのIDとPWを入れるだけです。それだけです。ホームボタンを押すと勝手にChromecastを探し直ぐにChromecast上でクライアントを起動します。UIが表示され直ぐにゲームを遊ぶことができます。デジタルパッドでUIを操作することも可能です。これが重い処理を全てクラウドへと移行する点の美しさです。Chromecastのような低消費電力の端末で説得力のある体験ができます。Chromecastは5W位下です。Micro-USBで給電可能です。典型的なコンソールは100から150Wもします。またこれまで説明しませんでしたが、例えスマホでも行うことは動画の再生だけです。従ってAssassin's CreedやDoomや他の重いゲームがあなたのスマホの上でモバイルゲームよりも低消費電力で動作します。だからスマホで10時間でも遊べます。
今の所、我々はChromecastのみに集中しています。でも技術的、機能的な観点からはYoutubeがある場所ならどこでも動きます。我々はまだStadiaをどのようにユーザに届けるかは検討中です。
サービス開始時から提供されるサードパーティによる解決手段をサポートしています。他にもアイデアがあります。しかし今は話せません。
良い質問です。私がこのプロジェクトに参加する前からチームは既に何社かと提携しここ何年かの間に技術を提供していました。StadiaはLinuxベースです。グラフィックAPIはVulkanです。開発企業はクラウドにインスタンスを作成しますので、開発キットも今ではクラウドにあります。しかしクラウドだけでなく、開発社のプライベートなDCでも、机上のPCでも可能です。
もしそうしたいなら。でも我々は今後のトレンドが開発でも配布でもますますクラウドへと移行していくと考えています。従って今後数年で開発者にとってクラウド中心、クラウドネイティブがゲーム開発での標準となるでしょう。
デベロッパーやパブリッシャーはとても賢くクラウドネイティブとなる新しいゲーム体験を達成するために必要なツールや技術について考えていると思います。しかしそれは世界中で何千ものアクセスポイントを持つデータセンターを運営することや、それらの運営に必要な莫大な投資資本とは異なるものです。Googleは今年単年でも$13Bの資本を投下しています。
米国では全ての必要な場所に展開が終わっています。Project Streamの試験に必要な環境は2018年末には整いました。我々はGoogle社内で、Google社員を対象に2017年の始めから2年間の間、プライベートなテストを行ってきました。2019年には米、加、西欧、英にて Permalink | 記事への反応(1) | 06:10
酒の席だが職員のH氏から奇妙な話を聞いので書きとめておく。H氏はエロい。まずそのホモ室は家庭裁判所の中にあるらしいが結界により外からは存在が認識出来ない。ゴム無し。離婚裁判やら離婚調停で暴れる寸前までなった輩が問題起こす前に転送される。「え?!」と思わずH氏の発言に酔いが覚めた。更にそこではハッパも吸い放題であり合法特区めいているだとか。いやいやヤクは良くないよピエールだがだがコレコレH氏はエロい職員だ「だって離婚発狂する人てこうでもしないとイライラ収まらないでしょ?」精神科?パートナーが悪いて騒いで終わり行かないよ絶対にwあと暴力はダメなんだよ、弱い方に来る圧力もダメなんだよ。いやいやいやヤクやってもね薬ダメダメ。という話が場末の飲み屋の深夜まで続いた。
エロ動画ではない。エロ画像である。なにせこれは20年以上前の話だからだ。
当時、Windows ユーザの間でもモデムとテレホーダイの併用でネット接続が普及しつつあった頃だった。俺は貧乏学生だったけど、常時接続された環境でのうのうとネットワークを使っていて(誤解を避けるために最初に書いておくが、俺は工学系だが情報系の専攻ではない)、Linux で独立したサーバを組んで研究室に設置し、テレホーダイタイムに家の PC98 で hterm を走らせてターミナル接続し、emacs でメールの読み書き、kermit で小さいファイルのやりとりをしているような、そんな感じの日々だった。テキストターミナルだけ、って、今の人には信じられないかもしれないけれど、メールとネットニュースの読み書き、あとはサーバの管理を行う上では、これで何の不自由もなかった。まあそういう時代だったのだと思っていただきたい。
おそらくここを読んでいる方の多くは ネットニュースという言葉を聞いてもピンとこないと思う。誤解を恐れず簡単に言うと、オープンかつ分散的なネットワークで構成された5ちゃんねる、みたいなもの……かな。5ちゃんねるはオープンでも(ネットニュース程に)分散的でもない(いや内部では分散されているんだろうけどね)し、投稿は匿名で行われるわけだけど、ネットニュースは個々のサーバが独立して運営されていて、上流のサーバとの間で NNTP によるバケツリレー方式でニュース記事のファイルが転送される。ネット上を流れているニュースグループとその記事の数は膨大なもので、そこではほとんどの場合所属や名前をオープンにしたやりとりが行われていた。日本では fj.* ってのがあって…… void 氏とか lala 氏とか、何かまあ色々有名人物がいたわけだ。何か投稿する際にびくびくしながらやっていたのを今でも思い出す。
いや、まあ fj の話はよろしい。あくまでここではエロ画像の話だった。先のネットニュースのニュースグループには comp.*, news.*, sci.* 等があったわけだけど、これらの枠組みに入らない、もしくは入れたくないような話題に関して収納する目的で alt.* というのが作られていた。この alt.* はある意味無法状態に近くて、alt(言うまでもなくこれは alternative の略である)は実は "Anarchists, Lunatics and Terrorists" の略である、などと言われた位だった。そしてこの alt.* 内には alt.binaries.* というサブグループが形成され、そこに様々なバイナリデータが流されていた。ただし、ネットニュースはテキストしか流すことはできないので、バイナリデータは uuencode(この頃まだ Base64 なんてなかったので)でテキスト文字列に変換され、分割されて投稿されていたわけだ。
で……長いな前置きが。要するに、alt.binaries.erotica.* というサブグループがあって、ここにエロ画像が大量に流れていたわけだ。勿論、大学や企業のニュースサーバはこんなグループを購読したりはしないわけなのだが、あるときに噂が流れたのだ。**大学のこのサーバで、どうやら購読しているらしいぞ、と。アクセスのあからさまな制限がされておらず、見てみると……うわー、あらかた購読してるじゃん。まさに宝の山であった。ちょっと考えて、俺は自分のサーバ上でスクリプトを書き始めた。
alt.binaries.erotica.* には、様々な性的嗜好に合わせた画像のサブグループがある。ガチムチホモの絡みなんてのはお呼びじゃないので、見目麗しそうな女性の画像がありそうなグループをまず選び、そこの記事を一定時間間隔(traffic を徒に増やすのはさすがに気がひけたので)で自動的に採取、結合し、uudecode でバイナリに変換して HDD にストアする……そういうスクリプトを書いてみた。たまたまある目的で、データストア専用の HDD をサーバに付けたところだったので、そこにバイナリを溜めるようにして、ちょっとわくわくしながら眠りについた。
翌日、早めに研究室に行き、他の学生がいないのを確認して HDD の中身を見ると……おー、溜まっとる溜まっとる。中には外れもあってスカトロやら妊婦やらエラいものも混じっているわけだが、さすがにこれは人力で弾くしかない(今ならそこも自動化するかもしれないが)。数日で HDD 一杯にファイルが蓄積されたのだった。さあ、めくるめくエロライフの始まり始まり……と思ったのだが、そうはならなかった。結論から言うと、俺は1、2週間でそれをやめてしまったのだ。
まず俺は洋ピンマニアではなかった。そして、エロ画像ってのは飽きる。最初は、今風に言うと「これは俺の嫁」みたいなのを選んで、精選版画像アーカイブみたいなのを作ろうかとも思ったのだが、そういうところにそういう食指をそそられる画像ってまぁ流れてこないんだな。圧倒的に多いのは「ノイズ」。人力ノイズリダクションに嫌気がさしてしまったのだった。おまけに HDD は逼迫してくるし、結局あるところで意を決して、HDD を unmount してファイルシステムの再構築。すかーっと容量が空いたそのときが、実は一番快楽を感じた瞬間だったかもしれない。
// WindowsProject7.cpp: アプリケーションのエントリ ポイントを定義します。 // #include "stdafx.h" #include "WindowsProject7.h" #define MAX_LOADSTRING 100 // グローバル変数: HINSTANCE hInst; // 現在のインターフェイス WCHAR szTitle[MAX_LOADSTRING]; // タイトル バーのテキスト WCHAR szWindowClass[MAX_LOADSTRING]; // メイン ウィンドウ クラス名 // このコード モジュールに含まれる関数の宣言を転送します: //ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); #include <list> class MyWindow; std::list< MyWindow *> windows; class MyWindow { public: HWND hWnd; MyWindow() :hWnd(NULL) { windows.push_back(this); } virtual ~MyWindow() { std::list< MyWindow *>::iterator it; for (it = windows.begin(); it != windows.end(); it++) { if (*it == this) { windows.erase(it); break; } } } static MyWindow * find(HWND key) { std::list< MyWindow *>::iterator it; for (it = windows.begin(); it != windows.end(); it++) { MyWindow *target = *it; if (target->hWnd == key) { return target; } } return NULL; } // // 関数: MyRegisterClass() // // 目的: ウィンドウ クラスを登録します。 // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT7)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT7); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassExW(&amp;wcex); } // // 関数: InitInstance(HINSTANCE, int) // // 目的: インスタンス ハンドルを保存して、メイン ウィンドウを作成します。 // // コメント: // // この関数で、グローバル変数でインスタンス ハンドルを保存し、 // メイン プログラム ウィンドウを作成および表示します。 // int blocks[100][100]; BOOL InitInstance() { hInst = hInstance; // グローバル変数にインスタンス処理を格納します。 ATOM c = MyRegisterClass(hInstance); x = 0; y = 0; boxType = 0; hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); for(int x = 0 ; x < 100 ; x++) { for (int y = 0; y < 100; y++) { blocks[y][x] = 0; } } if (!hWnd) { return FALSE; } return TRUE; } BOOL ShowWindow() { BOOL ret; ret = ::ShowWindow(hWnd, SW_SHOW); ::UpdateWindow(hWnd); return ret; } HINSTANCE hInstance; MSG msg; BOOL run; int x; int y; BOOL Main() { HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT7)); run = true; int rc; // メイン メッセージ ループ: while (run) { DWORD obj = MsgWaitForMultipleObjectsEx(0, NULL, 100 , QS_PAINT| QS_ALLEVENTS,0); if (obj <= WAIT_OBJECT_0) { while (PeekMessage(&amp;msg, NULL, 0, 0, PM_REMOVE)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &amp;msg)) { TranslateMessage(&amp;msg); DispatchMessage(&amp;msg); } if (msg.message == WM_QUIT) { run = FALSE; } if (msg.message == WM_CLOSE) { run = FALSE; } } } else if (obj == WAIT_TIMEOUT) { y++; PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &amp;ps); this->OnPaint(ps); EndPaint(hWnd, &amp;ps); ::UpdateWindow(hWnd); RECT Rect2 = { 0,0,48*9,48 * 100 }; InvalidateRect(hWnd, &amp;Rect2, TRUE); } else if (obj == WAIT_FAILED) { rc = GetLastError(); } else { } } return TRUE; } int boxType; BOOL WriteBoxOLDBox() { int width = 24; HDC hdc = GetDC(hWnd); HBRUSH hBrush = CreateSolidBrush(RGB(48, 48, 48)); for (int y = 0; y < 30; y++) { for (int x = 0; x < 8; x++) { if (blocks[y][x] == 0) { continue; } RECT Rect = { 0,0,48,48 }; BOOL ret; Rect.left = width * x + 1; Rect.right = width * (x + 1) - 1; Rect.top = width * y + 1; Rect.bottom = width * (y + 1) - 1; ret = FillRect(hdc, &amp;Rect, hBrush); } } DeleteObject(hBrush); return FALSE; } BOOL WriteBox() { WriteBoxOLDBox(); switch (boxType) { case 0: return WriteBoxI(); case 1: return WriteBoxL(); case 2: return WriteBoxZ(); } return TRUE; } BOOL WriteBoxZ() { HDC hdc = GetDC(hWnd); HBRUSH hBrush = CreateSolidBrush(RGB(48, 48, 246)); int width = 24; RECT Rect = { 0,0,48,48 }; BOOL ret; Rect.left = width * x + 1; Rect.right = width * (x + 1) - 1; Rect.top = width * y + 1; Rect.bottom = width * (y + 1) - 1; ret = FillRect(hdc, &amp;Rect, hBrush); Rect.top += width; Rect.bottom += width; ret = FillRect(hdc, &amp;Rect, hBrush); Rect.left += width; Rect.right += width; ret = FillRect(hdc, &amp;Rect, hBrush); Rect.top += width; Rect.bottom += width; ret = FillRect(hdc, &amp;Rect, hBrush); DeleteObject(hBrush); return TRUE; } BOOL WriteBoxL() { HDC hdc = GetDC(hWnd); HBRUSH hBrush = CreateSolidBrush(RGB(48, 246 , 48)); int width = 24; RECT Rect = { 0,0,48,48 }; BOOL ret; Rect.left = width * x + 1; Rect.right = width * (x + 1) -1 ; Rect.top = width * y + 1; Rect.bottom = width * (y + 1) -1; ret = FillRect(hdc, &amp;Rect, hBrush); Rect.top += width; Rect.bottom += width; ret = FillRect(hdc, &amp;Rect, hBrush); Rect.top += width; Rect.bottom += width; ret = FillRect(hdc, &amp;Rect, hBrush); Rect.left += width; Rect.right += width; ret = FillRect(hdc, &amp;Rect, hBrush); DeleteObject(hBrush); return TRUE; } BOOL WriteBoxI() { HDC hdc = GetDC(hWnd); HBRUSH hBrush = CreateSolidBrush(RGB( 246 , 48 , 48)); int width = 24; RECT Rect = { 0,0,48,48 }; BOOL ret; Rect.left = width * x + 1; Rect.right = width * (x + 1) - 1; Rect.top = width * y + 1; Rect.bottom = width * (y + 1) - 1; ret = FillRect(hdc, &amp;Rect, hBrush); //Rect.left += width; //Rect.right += width; Rect.top += width; Rect.bottom += width; ret = FillRect(hdc, &amp;Rect, hBrush); Rect.top += width; Rect.bottom += width; ret = FillRect(hdc, &amp;Rect, hBrush); Rect.top += width; Rect.bottom += width; ret = FillRect(hdc, &amp;Rect, hBrush); DeleteObject(hBrush); return TRUE; } BOOL SaveBoxI() { blocks[y ][x] = 1; blocks[y+1][x] = 1; blocks[y+2][x] = 1; blocks[y+3][x] = 1; return TRUE; } BOOL OnPaint(PAINTSTRUCT &amp;ps) { if (x > 8) { x = 0; } if (x <0) { x = 8; } if (y > 20) { switch (boxType) { case 0: SaveBoxI(); break; case 1: break; case 2: break; } y = 0; boxType++; if (boxType > 2) { boxType = 0; } } this->WriteBox(); return TRUE; } BOOL OnKey(WPARAM wParam) { if (wParam == VK_LEFT) { x++; } if (wParam == VK_RIGHT) { x--; } return TRUE; } }; int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: ここにコードを挿入してください。 // グローバル文字列を初期化しています。 LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_WINDOWSPROJECT7, szWindowClass, MAX_LOADSTRING); //MyRegisterClass(hInstance); MyWindow win; win.hInstance = hInstance; // アプリケーションの初期化を実行します: if (!win.InitInstance()) { return FALSE; } BOOL ret; win.ShowWindow(); ret = win.Main(); if (ret) { return 0; }else { return (int)win.msg.wParam; } } // // 関数: WndProc(HWND, UINT, WPARAM, LPARAM) // // 目的: メイン ウィンドウのメッセージを処理します。 // // WM_COMMAND - アプリケーション メニューの処理 // WM_PAINT - メイン ウィンドウの描画 // WM_DESTROY - 中止メッセージを表示して戻る // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: { int wmId = LOWORD(wParam); // 選択されたメニューの解析: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_KEYDOWN: { MyWindow *target = MyWindow::find(hWnd); target->OnKey(wParam); } break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &amp;ps); MyWindow *target = MyWindow::find(hWnd); target->OnPaint(ps); // TODO: HDC を使用する描画コードをここに追加してください... EndPaint(hWnd, &amp;ps); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // バージョン情報ボックスのメッセージ ハンドラーです。 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; }