Unity 戦闘でシミュレートされた空飛ぶ円盤
Unity は単純な空飛ぶ円盤ゲームを実装します。
プロジェクトアドレス
Unity UFO 実現効果
ゲームのルール
-
合計 10 ラウンド、各ラウンド 10 秒
-
ラウンドが高いほど、難易度が高くなります。
-
各ラウンドの特定の時間に特定の数の空飛ぶ円盤が生成されます. 異なる色とサイズの 4 つの空飛ぶ円盤があり、異なる空飛ぶ円盤は異なる飛行速度と異なるスコアを持っています.
ディスク スコア 赤 2 黄色 4 青 6 緑 8 -
キネマティクス モードとダイナミクス モードの 2 つのモードがあります。
コード
アクション
FlyActionManager
: アクションを管理するためのインターフェース. キネマティクスとフィジックスのアクション管理は、このアクションを継承して実装する必要があります.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface FlyActionManager
{
void FlyDisk(GameObject disk, float speed, Vector3 direction);
}
CCFlyAction
: キネマティック アクション
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCFlyAction : SSAction
{
Vector3 direction;
float speed;
public static CCFlyAction GetSSAction(Vector3 direction, float speed){
CCFlyAction index = ScriptableObject.CreateInstance<CCFlyAction>();
index.direction = direction;
index.speed = speed;
return index;
}
// Start is called before the first frame update
public override void Start()
{
// gameObject.AddComponent<Rigidbody>();
gameObject.GetComponent<Rigidbody>().isKinematic = true;
}
// Update is called once per frame
public override void Update()
{
transform.Translate(direction*speed*Time.deltaTime);
if(this.transform.position.x < -35 || this.transform.position.x > 35 ||
this.transform.position.y < -10 || this.transform.position.y > 30){
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
CCActionManager
: キネマティック ムーブメント管理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CCActionManager : SSActionManager, ISSActionCallback, FlyActionManager
{
public FirstController controller;
public CCFlyAction fly;
// Start is called before the first frame update
public void Start()
{
controller = (FirstController)SSDirector.GetInstance().CurrentSceneController;
}
// Update is called once per frame
// public void Update()
// {
// }
public void FlyDisk(GameObject disk, float speed, Vector3 direction){
fly = CCFlyAction.GetSSAction(direction, speed);
RunAction(disk, fly, this);
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Completed,
int intParam = 0,
string strParam = null,
Object objectParam = null){
controller.diskFactory.FreeDisk(source.gameObject);
}
}
PhysicalFlyAction
: 物理的なアクション
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PhysicalFlyAction : SSAction
{
Vector3 direction;
float speed;
public static PhysicalFlyAction GetSSAction(Vector3 direction, float speed){
PhysicalFlyAction index = ScriptableObject.CreateInstance<PhysicalFlyAction>();
index.direction = direction;
index.speed = speed;
return index;
}
// Start is called before the first frame update
public override void Start()
{
// gameObject.AddComponent<Rigidbody>();
gameObject.GetComponent<Rigidbody>().isKinematic = false;
gameObject.GetComponent<Rigidbody>().velocity = speed*direction; // 水平初速度
}
// Update is called once per frame
public override void Update()
{
if(transform != null){
transform.Translate(direction*speed*Time.deltaTime);
if(this.transform.position.y < -10){
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
}
PhysicalActionManager
: 物理モーション管理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PhysicalActionManager : SSActionManager, ISSActionCallback, FlyActionManager
{
public FirstController controller;
public PhysicalFlyAction fly;
// Start is called before the first frame update
public void Start()
{
controller = (FirstController)SSDirector.GetInstance().CurrentSceneController;
}
// Update is called once per frame
// void Update()
// {
// }
public void FlyDisk(GameObject disk, float speed, Vector3 direction){
fly = PhysicalFlyAction.GetSSAction(direction, speed);
RunAction(disk, fly, this);
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Completed,
int intParam = 0,
string strParam = null,
Object objectParam = null){
controller.diskFactory.FreeDisk(source.gameObject);
}
}
コントローラ
これは単一インスタンス モードで、コードは次のとおりです。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T: MonoBehaviour
{
protected static T instance;
public static T Instance {
get {
if (instance == null) {
instance = (T)FindObjectOfType (typeof(T));
if (instance == null) {
Debug.LogError ("An instance of " + typeof(T) +
" is needed in the scene, but there is none.");
}
}
return instance;
}
}
}
DiskData
: 空飛ぶ円盤に関連する変数をカプセル化します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiskData : MonoBehaviour
{
public float speed;
public Vector3 direction;
public int score;
}
DiskFactory
: オブジェクト プールの実装
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DiskFactory : MonoBehaviour
{
private List<DiskData> used;
private List<DiskData> free;
public GameObject disk;
// Start is called before the first frame update
void Start()
{
used = new List<DiskData>();
free = new List<DiskData>();
disk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"));
disk.SetActive(false);
}
// Update is called once per frame
void Update()
{
}
public GameObject GetDisk(int round){
GameObject d;
if(free.Count > 0){
d = free[0].gameObject;
free.Remove(free[0]);
}
else{
d = GameObject.Instantiate<GameObject>(disk, Vector3.zero, Quaternion.identity);
d.AddComponent<DiskData>();
}
d.gameObject.name = "Disk";
// 根据不同的round设定不同的初始数值
// round越大,难度越高,则可以通过设置不同的速度改变难度
float uspeed = 2 + round;
// scale
Vector3 uscale = new Vector3(6, 1, 6);
// 随机初始方向
float dir_y = Random.Range(-0.5f, 0.5f);
int dir_x = Random.Range(0, 2);
// 随机颜色,不同的颜色有不同的速度
int color = Random.Range(1, 5);
// 红色
if(color == 1){
d.GetComponent<Renderer>().material.color = Color.red;
d.GetComponent<DiskData>().speed = 0.4f * uspeed;
d.GetComponent<DiskData>().direction = new Vector3(dir_x, dir_y, 0);
d.GetComponent<DiskData>().score = 2;
d.GetComponent<Transform>().localScale = 0.7f*uscale;
}
// 黄色
else if(color == 2){
d.GetComponent<Renderer>().material.color = Color.yellow;
d.GetComponent<DiskData>().speed = 0.8f * uspeed;
d.GetComponent<DiskData>().direction = new Vector3(dir_x, dir_y, 0);
d.GetComponent<DiskData>().score = 4;
d.GetComponent<Transform>().localScale = 0.6f*uscale;
}
// 蓝色
else if(color == 3){
d.GetComponent<Renderer>().material.color = Color.blue;
d.GetComponent<DiskData>().speed = 1.2f * uspeed;
d.GetComponent<DiskData>().direction = new Vector3(dir_x, dir_y, 0);
d.GetComponent<DiskData>().score = 6;
d.GetComponent<Transform>().localScale = 0.5f*uscale;
}
// 绿色
else{
d.GetComponent<Renderer>().material.color = Color.green;
d.GetComponent<DiskData>().speed = 1.6f * uspeed;
d.GetComponent<DiskData>().direction = new Vector3(dir_x, dir_y, 0);
d.GetComponent<DiskData>().score = 8;
d.GetComponent<Transform>().localScale = 0.4f*uscale;
}
used.Add(d.GetComponent<DiskData>());
return d;
}
public void FreeDisk(GameObject d){
foreach(DiskData dd in used){
if(dd.gameObject.GetInstanceID() == d.GetInstanceID()){
d.SetActive(false);
used.Remove(dd);
free.Add(dd);
break;
}
}
}
public void Clear(){
foreach(DiskData dd in used){
dd.gameObject.transform.position = new Vector3(-100, 0, 0); // 将飞碟的位置设置为边界外,这样就能及时销毁
Destroy(dd.gameObject);
}
foreach(DiskData dd in free){
dd.gameObject.transform.position = new Vector3(-100, 0, 0); // 将飞碟的位置设置为边界外,这样就能及时销毁
Destroy(dd.gameObject);
}
used.Clear();
free.Clear();
}
}
ScoreRecorder
: スコアリング コントローラー。各ラウンドのスコアの計算とリセットを担当します。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScoreRecorder : System.Object
{
public int score;
// Start is called before the first frame update
void Start()
{
score = 0;
}
// Update is called once per frame
void Update()
{
}
public void Record(GameObject disk){
score += disk.GetComponent<DiskData>().score;
}
public void Reset(){
score = 0;
}
}
FirstController
: データの読み込み、空飛ぶ円盤の送信、ゲームの再起動などを担当するシーン コントローラー。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
// Start is called before the first frame update
public DiskFactory diskFactory;
public ScoreRecorder scoreRecorder;
public FlyActionManager flyActionManager;
public UserGUI userGUI;
int round;
float current_time;
float index_time;
int round_disk_count;
static float round_time = 10;
static int max_disk = 30;
void Start()
{
SSDirector.GetInstance().CurrentSceneController = this;
gameObject.AddComponent<DiskFactory>();
gameObject.AddComponent<CCActionManager>();
gameObject.AddComponent<PhysicalActionManager>();
userGUI = gameObject.AddComponent<UserGUI>();
diskFactory = Singleton<DiskFactory>.Instance;
flyActionManager = Singleton<CCActionManager>.Instance;
LoadResources();
}
// Update is called once per frame
void Update()
{
if(userGUI.status == 1){
if(round <= 10){
current_time += Time.deltaTime;
index_time += Time.deltaTime;
if(current_time < round_time){ // 当前轮次没有结束
if(index_time > 1){
index_time = 0;
userGUI.round = round;
int disk_count = Random.Range(1,4);
while(disk_count > 0){
float x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
float y = Random.Range(0f, 8f);
SendDisk(x, y);
round_disk_count++;
disk_count--;
if(round_disk_count == max_disk){
break;
}
}
}
}
else{
round_disk_count = 0;
current_time = 0;
index_time = 0;
round++;
userGUI.round = round;
}
}
if(round >= 11){
userGUI.status = 2;
}
}
}
public void LoadResources(){
scoreRecorder = new ScoreRecorder();
scoreRecorder.Reset();
round = 1;
current_time = 0;
index_time = 0;
round_disk_count = 0;
}
public void SendDisk(float x, float y){
GameObject disk = diskFactory.GetDisk(round);
// float rand_y = Random.Range(0f, 8f);
// float rand_x = Random.Range(-1f, 1f) < 0 ? -1 : 1;
disk.transform.position = new Vector3(x, y, 0);
disk.SetActive(true);
flyActionManager.FlyDisk(disk, disk.GetComponent<DiskData>().speed, disk.GetComponent<DiskData>().direction);
}
public void GetHit(Vector3 position){
Ray ray = Camera.main.ScreenPointToRay(position);
RaycastHit[] hits = Physics.RaycastAll(ray);
for(int i = 0; i < hits.Length; i++){
RaycastHit hit = hits[i];
if (hit.collider.gameObject.GetComponent<DiskData>() != null){
GameObject disk = hit.collider.gameObject;
disk.transform.position = new Vector3(0, -20, 0);
diskFactory.FreeDisk(disk);
scoreRecorder.Record(disk);
userGUI.score = scoreRecorder.score;
}
}
}
public void ClearAll(){
GameObject[] gameObjects = FindObjectsOfType(typeof(GameObject)) as GameObject[]; // 获取所有的GameObject
foreach(GameObject gmobj in gameObjects){
if(gmobj.name == "Disk"){
gmobj.gameObject.SetActive(false);
}
}
diskFactory.Clear();
}
public void Restart(){
round = 1;
current_time = 0;
index_time = 0;
round_disk_count = 0;
scoreRecorder.Reset();
userGUI.score = scoreRecorder.score;
userGUI.round = round;
ClearAll();
}
public void SetMode(int mode){
if(mode == 0){
flyActionManager = Singleton<CCActionManager>.Instance;
}
else if(mode == 1){
flyActionManager = Singleton<PhysicalActionManager>.Instance;
}
}
}
意見
UserGUI
: ゲームインターフェース
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
GUIStyle topicStyle;
GUIStyle dataStyle;
public int round;
public int score;
public int status;
public int mode;
private string[] modeStrings = {"运动学模式", "重力学模式"};
private IUserAction userAction;
// Start is called before the first frame update
void Start()
{
round = 1;
score = 0;
status = 0;
mode = 0;
userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;
SetStyle();
}
// Update is called once per frame
void Update()
{
}
void SetStyle(){
topicStyle = new GUIStyle();
topicStyle.normal.textColor = Color.black;
topicStyle.fontSize = 60;
dataStyle = new GUIStyle();
dataStyle.normal.textColor = Color.black;
dataStyle.fontSize = 30;
}
void OnGUI(){
GUI.Label(new Rect(300, 20, 50, 40), "Hit UFO", topicStyle);
if(status == 0){
if(GUI.Button(new Rect(10, 10, 100, 40), "Start Game")){
status = 1;
userAction.Restart();
}
// GUI.Box(new Rect(100, 100, 300, 200), "模式:");
// mode = 0;
mode = GUI.Toolbar(new Rect (10, 60, 200, 30), mode , modeStrings);
userAction.SetMode(mode);
}
else if(status == 1){
GUI.Label(new Rect(20, 20, 100, 50), "score: "+ score, dataStyle);
GUI.Label(new Rect(20, 80, 100, 50), "round: "+ round, dataStyle);
if(Input.GetButtonDown("Fire1")){
userAction.GetHit(Input.mousePosition);
}
if(GUI.Button(new Rect(20, 160, 100, 40), "Restart")){
status = 0;
userAction.Restart();
}
}
else if(status == 2){
GUI.Label(new Rect(320, 100, 50, 40), "Game Over", dataStyle);
GUI.Label(new Rect(320, 200, 50, 40), "total score: " + score, dataStyle);
if (GUI.Button(new Rect(330, 330, 100, 40), "Restart"))
{
status = 0;
userAction.Restart();
}
}
}
}