Unity实战之模拟飞碟
unity实现简易打飞碟游戏。
项目地址
Unity飞碟实现效果
游戏规则
-
一共10轮,每轮10s
-
轮次越高,难度越大。
-
每轮的一定时间都会生成一定数量的飞碟,一共有四种颜色大小不同的飞碟,不同的飞碟的飞行速度不一样,得分也不一样
disk score 红 2 黄 4 蓝 6 绿 8 -
一共有两种模式:运动学模式和动力学模式
代码实现
Action
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);
}
}
Controller
这里是单实例模式,代码如下:
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;
}
}
}
View
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();
}
}
}
}