Steamで開発者だけ最新の環境でテストプレイする方法

備忘録なので、走り書きをします。

①デポを確認し、標準のデポに加えてテスト用のデポを作成する

赤枠がテスト用のデポ

保存する

②パッケージ表示へ行く

画像の中に3つのパッケージがあり、一番下のSteam Store Packages Other Packagesがストアで公開されているパッケージ

上二つが開発者用とベータ版のパッケージとなる

③パッケージを編集するために、IDの数字部分をクリック

含まれるデポに、テスト用のデポを追加する(下図は既に追加した後の図)

④公開タブから公開する

⑤テスト用デポにアップロードする

※この時、通常のデポにアップしてしまうと全員に公開される

⑥ビルドタブに行き、アップロードしたものがテスト用デポに含まれているか確認する

下図ではビルド番号9245182の行

⑦最新のビルドをdefaultにする

⑧公開する

⑨Steamに開発者のアカウントでログインする

⑩ゲームがアップロードされるので、テストを実施する

以上です!

ストアで使っているデポにはテスト用のビルドが含まれないので、開発者だけが最新のデポを参照できるようです

【Unity講座】STG 制作講座 - 弾幕制作#1 -

初めに

Unityを使って弾幕STGを作るための講座です。

講座といってもまだまだUnity初心者ですので、初心者のアウトプットの場だとお考え下さい。

この記事は以下の動画でも説明しています。 そちらも合わせて確認して下さい。

www.youtube.com

環境

Unity 2020.3.34f1 VisualStudio2019 Cominity

Unityを起動します

UnityHubが起動するので、「新規作成」から以下のプロジェクトを作成します。

テンプレート:3D

プロジェクト名:STG_Game

Unityが立ち上がるとこんな感じです

弾の画像を取り取り込みます

今回使う画像はこちらです

imageフォルダを作成して、その中に画像をドラッグ&ドロップします

取り込んだ後に画像を選択して、画面右側のinspectorから画像の設定を変更します。

TextureTypeを[Default]→[Sprite(2D andUI)]に変更して、Applyを押します。

弾をシーンに配置します

Sceneビューになっていることを確認して、画像をシーン内にドラッグ&ドロップします。

Sceneに画像がbulletとして取り込まれました。

右側のInspectorからPositionをXYZを0,0,0に設定します。

  1. 弾を動かすスクリプトを書く

Scriptフォルダを作成して、その中に新規C# Scriptでスクリプトを作成し、名前をBulletにします。

Bulletをbullet_image_01にドラッグ&ドロップします。

下図のとおり、インスペクターからBullet(Script)がついていればOKです

Bulletスクリプトをダブルクリックして、VisualStuidoを立ち上げ、以下をコピペしてください

プログラム①

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    [SerializeField] float angle; // 角度
    [SerializeField] float speed; // 速度
    Vector3 velocity; // 移動量

    void Start()
    {
        // X方向の移動量を設定する
        velocity.x = speed * Mathf.Cos(angle * Mathf.Deg2Rad);

        // Y方向の移動量を設定する
        velocity.y = speed * Mathf.Sin(angle * Mathf.Deg2Rad);
        
        // 5秒後に削除
        Destroy(gameObject, 5.0f);
    }
    void Update()
    {
        // 毎フレーム、弾を移動させる
        transform.position += velocity * Time.deltaTime;
    }

}
    [SerializeField] float angle; // 角度
    [SerializeField] float speed; // 速度
    Vector3 velocity; // 移動量

角度と速度をfloatで定義しています。 SerializeFieldを設定することで、インスペクターから角度と速度を設定出来るようになります。 velocityは進む方向を内部で保持しておくための変数です。

    void Start()
    {
        // X方向の移動量を設定する
        velocity.x = speed * Mathf.Cos(angle * Mathf.Deg2Rad);

        // Y方向の移動量を設定する
        velocity.y = speed * Mathf.Sin(angle * Mathf.Deg2Rad);
        
        // 5秒後に削除
        Destroy(gameObject, 5.0f);
    }

StartはこのGameObjectが生成された時に一度だけ呼び出されます。 角度と速度をもとに、ここで弾が進む方向を設定します。

ここの部分はいろいろ書いていますが、ややこしいのでコピペで良いと思います。 詳しく知りたい方は「弾幕 三角関数」で検索してください。

DeleteObjectは弾を一定時間後に削除する機能になります。 ここでは5秒後に削除するようにしています。

    void Update()
    {
        // 毎フレーム、弾を移動させる
        transform.position += velocity * Time.deltaTime;
    }

Updateです。 ここは毎フレーム呼ばれる処理になっています。 弾の座標にヴェロシティを加算することで弾の移動を実現しています。

Time.deltaTimeとありますが、これは直前のフレームと今のフレームの差の値になります。 今後も出てくる重要なパラメータになりますが、今回は説明を割愛します。

動作確認

インスペクターからbullet_image_01を見ると、角度と速度が設定出来るようになっています。 シリアライズフィールドの効果で、インスペクターから値を設定出来るようになりました。

angleとspeedを変更して、どのように動くか確認してみてください。

画面上部のプレイボタンを押したら実行できます。

バグ修正

angle180で動作させると、弾が上正面を向いたまま左に動くと思います。

angleに合わせて弾の向きを修正するようにしましょう。

  • プログラム②
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Bullet : MonoBehaviour
{
    [SerializeField] float angle; // 角度
    [SerializeField] float speed; // 速度
    Vector3 velocity; // 移動量

    void Start()
    {
        // X方向の移動量を設定する
        velocity.x = speed * Mathf.Cos(angle * Mathf.Deg2Rad);

        // Y方向の移動量を設定する
        velocity.y = speed * Mathf.Sin(angle * Mathf.Deg2Rad);
        
        // 弾の向きを設定する
        float zAngle = Mathf.Atan2(velocity.y, velocity.x) * Mathf.Rad2Deg - 90.0f;
        transform.rotation = Quaternion.Euler(0, 0, zAngle);
        
        // 5秒後に削除
        Destroy(gameObject, 5.0f);
    }
    void Update()
    {
        // 毎フレーム、弾を移動させる
        transform.position += velocity * Time.deltaTime;
    }

}

以下をstartに追加してください。

        // 弾の向きを設定する
        float zAngle = Mathf.Atan2(velocity.y, velocity.x) * Mathf.Rad2Deg - 90.0f;
        transform.rotation = Quaternion.Euler(0, 0, zAngle);

atanは2つの成分から角度を求める関数です xの移動量とyの移動量、つまり移動方向から弾の画像を回転させてます。

いろいろなangleで実行してください。全部弾の進む方向に向いていると思います。

これで完成です!!

弾が1つ完成したので、次回は弾を沢山出して弾幕の様にしていこうと思います。

プロジェクト配布

以下のGドライブに本プロジェクトを配布します。

STG_Game\Assets\Scenes\SampleScene.unityをダブルクリックで開くことが出来ると思います。

Libraryフォルダを削除しているので、起動に時間が掛かります。

●免責 このファイルを使用することにより発生したいかなる損害についても、責任を負いません。

drive.google.com

SteamAPIのLeaderboardsでスコア以外の追加情報をアップロードする

UploadLeaderboardScoreのpScoreDetailsの使い方についてです。

pScoreDetailsはnScore以外の追加情報を設定できます。

例えば、スコアをアップロードした日付や、ゲーム内でのパラメータ(ランクやレベル)といった情報です。

関連ソースコードは以下です。


struct ScoreDetails {
    int val1 = 60;
    int val2 = 120;

    int peakRank = 999;
    int suvirvalDay = 200;
    long long Score = 123456789123456;
};

bool CSteamLeaderboards::UploadScore(int score)
{
    if (!m_CurrentLeaderboard)
        return false;

    const int32* pScoreDetails  = (int32*) new ScoreDetails();

    SteamAPICall_t hSteamAPICall =
        SteamUserStats()->UploadLeaderboardScore(m_CurrentLeaderboard, k_ELeaderboardUploadScoreMethodKeepBest, score, pScoreDetails, 6);

    m_callResultUploadScore.Set(hSteamAPICall, this, &CSteamLeaderboards::OnUploadScore);

    return true;
}

void CSteamLeaderboards::OnUploadScore(LeaderboardScoreUploaded_t* pCallback, bool bIOFailure)
{
    if (!pCallback->m_bSuccess || bIOFailure)
    {
        int aaa = 0;
        aaa++;
    }
}

bool CSteamLeaderboards::DownloadScores()
{
    if (!m_CurrentLeaderboard)
        return false;

    // 現在のユーザー周辺の指定されたランキングデータを読み込む
    SteamAPICall_t hSteamAPICall = SteamUserStats()->DownloadLeaderboardEntries(
        m_CurrentLeaderboard, k_ELeaderboardDataRequestGlobalAroundUser, -4, 5);
    m_callResultDownloadScore.Set(hSteamAPICall, this, &CSteamLeaderboards::OnDownloadScore);

    return true;
}


void CSteamLeaderboards::OnDownloadScore(LeaderboardScoresDownloaded_t* pCallback, bool bIOFailure)
{
    if (!bIOFailure)
    {
        m_nLeaderboardEntries = std::min(pCallback->m_cEntryCount, 10);

        int32* pScoreDetails = (int32*) new ScoreDetails();

        for (int index = 0; index < m_nLeaderboardEntries; index++)
        {
            SteamUserStats()->GetDownloadedLeaderboardEntry(
                pCallback->m_hSteamLeaderboardEntries, index, &m_leaderboardEntries[index], pScoreDetails, 6);
        }

        ScoreDetails* ppp = (ScoreDetails*)pScoreDetails;

        int aaa = 0;
        aaa++;
    }
}

UploadLeaderboardScoreの第4引数にconst int32*にキャストしたScoreDetailsを入力しています。

UploadLeaderboardScoreの第5引数はScoreDetails構造体のintの数です。

今回の例だと、intが4つ、longlongが1つなので、int6個分なので6としています。


あとは、OnDownloadScoreで同様に取得します。

受け取るための構造体(int32*)のメモリを確保して、GetDownloadedLeaderboardEntryの第3引数に入力しています。

あとは、int32をScoreDetailsにキャストしているだけです。


これでアップロードした時のScoreDetailsの情報を取得できます!

STEAMAPIのCCallResult<CLeaderboards, LeaderboardFindResult_t>コールバックが来ない

STEAMAPIのスコアランキングを登録しようとしたときにハマりました

以下の記事を読み進めながら、Steamのリーダーボードを取得しようとしてもコールバック関数が呼ばれず困っていました partner.steamgames.com

あとサンプルも参考にしました

SteamSDK\steamworks_sdk_153a\sdk\steamworksexample\Leaderboards.cpp


↓呼び出し関数


    SteamAPICall_t hSteamAPICall = 0;

    hSteamAPICall = SteamUserStats()->FindLeaderboard("RankingName");

    CCallResult<CLeaderboards, LeaderboardFindResult_t> m_SteamCallResultCreateLeaderboard;
    m_SteamCallResultCreateLeaderboard.Set(hSteamAPICall, this, &CLeaderboards::OnFindLeaderboard);

↓これが呼ばれない!!!

void CLeaderboards::OnFindLeaderboard(LeaderboardFindResult_t* pFindLearderboardResult, bool bIOFailure)
{
    // see if we encountered an error during the call
    if (!pFindLearderboardResult->m_bLeaderboardFound || bIOFailure)
        return;

答えはここに書いてありました partner.steamgames.com

コールバックの処理
Steamのすべてのコールバックを確実に処理するために、新たなメッセージを定期的にチェックする必要があります。 これは、この呼び出しをゲームループに追加することで実現できます。
...
SteamAPI_RunCallbacks();
...

追記 上記で概ね準備は整ったのでいざ、スコアをアップロードするとコールバック内でエラーになってしまいました。 原因はリーダーボードがバグっていたようなので、作り直したら上手くいきました(解せないです!)


bool CSteamLeaderboards::UploadScore(int score)
{
    if (!m_CurrentLeaderboard)
        return false;

    SteamAPICall_t hSteamAPICall =
        SteamUserStats()->UploadLeaderboardScore(m_CurrentLeaderboard, k_ELeaderboardUploadScoreMethodKeepBest, score, NULL, 0);

    m_callResultUploadScore.Set(hSteamAPICall, this, &CSteamLeaderboards::OnUploadScore);

    return true;
}

void CSteamLeaderboards::OnUploadScore(LeaderboardScoreUploaded_t* pCallback, bool bIOFailure)
{
    if (!pCallback->m_bSuccess || bIOFailure)
    {
        int aaa = 0;
        aaa++; // ←ここに入ってくる = エラーでアップロード出来ていない
        //OutputDebugString("Score could not be uploaded to Steam\n");
    }
}

【備忘】MFCでの英語OSの動作の確認方法

MFCで作成したダイアログ(マルチバイトキャラクターセット)の検証方法は以下で可能です Windows10で試しました

①時刻と言語

②日付、時刻、地域の追加設定

③日付、時刻、数値形式の変更

④システムロケールの変更

これで日本語が文字ばけしていればOKだと思います

STGのプレイ時間の内訳

いろいろなSTGのプレイ時間の内訳を調べました。

計測期間 ステージ  操作可能になった瞬間~ボス戦のBGMが始まった瞬間 ボス  ボス戦のBGMが始まった瞬間~ボスの体力が0になった瞬間

上記の計測期間のため、ボスの撃破演出やリザルト表示時間は含まれていません。 また、裏ボス戦の時間も含まれていません。

調べた結果、1プレイはおおよそ20分前後ですね。 ボス戦も30秒~90秒ぐらいで、ゲームが進むほど長くなる傾向にありますね。

【Unity】Buttonをキーボードやパッドだけで操作するときに、マウス操作が邪魔

マウスを使わないゲームの開発時に、

キーボード操作だけでButtonを操作したいのに、ボタンをマウスで選択できちゃったり、マウスクリックでアクティブが外れちゃったりする問題があります。

 

ボタンをマウスで選択できちゃう問題の対応

ボタンをマウスで選択できなくするためには、ボタンの上にイメージを貼れば良いようです。

f:id:tsugumiyumeno:20220416203208p:plain

画像のアルファ値を0にすれば、見えなくなり解決

f:id:tsugumiyumeno:20220416203304p:plain

 

マウスクリックでアクティブが外れちゃったりする問題の対応

https://twitter.com/YumenoTsugumi/status/1515259764766171136?s=20&t=qBh01r4D7hb0IcA733bjpQ

アクティブなボタンがある時に、画面をクリックするとアクティブが外れてしまいます。

 

以下のようにアクティブなボタンを記憶しておいて、アクティブが無くなった時に記憶していたボタンで上書く感じでどうにかなるようです。

 

 

using UnityEngine.EventSystems;

 

    GameObject currentSelectedGameObject = null;
    void Update()
    {
        GameObject currentObject = EventSystem.current.currentSelectedGameObject;
        if (currentObject)
        {
            currentSelectedGameObject = currentObject;
        } 
        else
        {
            if(currentSelectedGameObject != null){
                EventSystem.current.SetSelectedGameObject(currentSelectedGameObject);
            }
        }
    }