相关资源
代码仓库 视频演示
游戏规则
游戏有 n 个 round,每个 round 都包括10 次 trial 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制 每个 trial 的飞碟有随机性,总体难度随 round 上升 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定
得分规则
条件 得分 飞碟为红色 加1分 飞碟为绿色 加2分 飞碟为蓝色 加3分 飞碟的size为1(小飞碟) 加3分 飞碟的size为2(中飞碟) 加2分 飞碟的size为3(大飞碟) 加1分 飞碟的速度为x 加x分
UML图
Model
DiskFactory.cs
是一个工厂类负责飞碟对象的创建和销毁,使用了对象池来实现缓存功能,当销毁一个飞碟对象时,其实是将其保存到了一个对象池中,当后面需要用到飞碟时,首先查找对象池中有无空闲的飞碟对象,如果有就直接取出,为其赋予需要的属性进行使用,如果没有才新建一个飞碟对象,从而减少创建、销毁对象的开销。
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
public class DiskFactory : MonoBehaviour {
public GameObject diskPrefab;
private List< DiskData> used;
private List< DiskData> free;
public void Start ( ) {
diskPrefab = GameObject. Instantiate ( Resources. Load < GameObject> ( "Prefabs/Disk" ) , Vector3. zero, Quaternion. identity) ;
diskPrefab. SetActive ( false ) ;
used = new List< DiskData> ( ) ;
free = new List< DiskData> ( ) ;
}
public GameObject GetDisk ( Ruler ruler) {
GameObject disk;
int diskNum = free. Count;
if ( diskNum == 0 ) {
disk = GameObject. Instantiate ( diskPrefab, Vector3. zero, Quaternion. identity) ;
disk. AddComponent ( typeof ( DiskData ) ) ;
}
else {
disk = free[ diskNum - 1 ] . gameObject;
free. Remove ( free[ diskNum - 1 ] ) ;
}
disk. GetComponent < DiskData> ( ) . speed = ruler. speed;
disk. GetComponent < DiskData> ( ) . color = ruler. color;
disk. GetComponent < DiskData> ( ) . size = ruler. size;
if ( ruler. color == "red" ) {
disk. GetComponent < Renderer> ( ) . material. color = Color. red;
}
else if ( ruler. color == "green" ) {
disk. GetComponent < Renderer> ( ) . material. color = Color. green;
}
else {
disk. GetComponent < Renderer> ( ) . material. color = Color. blue;
}
disk. transform. localScale = new Vector3 ( 1.2f , 0.1f * ( float ) ruler. size, 1.2f ) ;
disk. transform. position = ruler. beginPos;
disk. SetActive ( true ) ;
used. Add ( disk. GetComponent < DiskData> ( ) ) ;
return disk;
}
public void FreeDisk ( GameObject disk) {
foreach ( DiskData d in used) {
if ( d. gameObject. GetInstanceID ( ) == disk. GetInstanceID ( ) ) {
disk. SetActive ( false ) ;
used. Remove ( d) ;
free. Add ( d) ;
break ;
}
}
}
}
DiskData.cs
是一个定义了飞碟的大小、颜色、速度的类
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
public class DiskData : MonoBehaviour {
public int size;
public string color;
public int speed;
}
Singleton
定义了单实例模型,因为在本项目中,DiskFactory
是采用单实例的设计模式,整个项目只需要有一个DiskFactory,所以要定义一个单实例模型用以创建DiskFactory的单实例
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;
}
}
}
ScoreRecorder
用以记录分数的类,实现前面定义的得分规则
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
public class ScoreRecorder {
public int score;
public ScoreRecorder ( ) {
score = 0 ;
}
public void Record ( DiskData disk) {
int diskSize = disk. size;
switch ( diskSize) {
case 1 :
score += 3 ;
break ;
case 2 :
score += 2 ;
break ;
case 3 :
score += 1 ;
break ;
default : break ;
}
score += disk. speed;
string diskColor = disk. color;
if ( diskColor. CompareTo ( "red" ) == 0 ) {
score += 1 ;
}
else if ( diskColor. CompareTo ( "green" ) == 0 ) {
score += 2 ;
}
else if ( diskColor. CompareTo ( "blue" ) == 0 ) {
score += 3 ;
}
}
public void Reset ( ) {
score = 0 ;
}
}
Ruler
定义了每个飞碟的属性,也保存了当前的回合等信息,用以规定飞船的获取规则
public struct Ruler {
public int trialNum;
public int roundNum;
public int roundSum;
public int [ ] roundDisksNum;
public float sendTime;
public int size;
public int speed;
public string color;
public Vector3 direction;
public Vector3 beginPos;
} ;
Controller
RoundController
是一个回合控制器,主要用以控制每个回合中飞碟的创建、发射、销毁,并且控制飞碟对象的运动类型为运动学运动或物理运动
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
public struct Ruler {
public int trialNum;
public int roundNum;
public int roundSum;
public int [ ] roundDisksNum;
public float sendTime;
public int size;
public int speed;
public string color;
public Vector3 direction;
public Vector3 beginPos;
} ;
public class RoundController : MonoBehaviour {
private IActionManager actionManager;
private ScoreRecorder scoreRecorder;
private MainController mainController;
private Ruler ruler;
void Start ( ) {
actionManager = gameObject. AddComponent < CCActionManager> ( ) ;
gameObject. AddComponent < PhysisActionManager> ( ) ;
scoreRecorder = new ScoreRecorder ( ) ;
mainController = Director. GetInstance ( ) . mainController;
gameObject. AddComponent < DiskFactory> ( ) ;
InitRuler ( ) ;
}
void InitRuler ( ) {
ruler. trialNum = 0 ;
ruler. roundNum = 0 ;
ruler. sendTime = 0 ;
ruler. roundDisksNum = new int [ 10 ] ;
generateRoundDisksNum ( ) ;
}
public void generateRoundDisksNum ( ) {
for ( int i = 0 ; i < 10 ; ++ i) {
ruler. roundDisksNum[ i] = Random. Range ( 0 , 4 ) + 1 ;
}
}
public void Reset ( ) {
InitRuler ( ) ;
scoreRecorder. Reset ( ) ;
}
public void Record ( DiskData disk) {
scoreRecorder. Record ( disk) ;
}
public int GetScores ( ) {
return scoreRecorder. score;
}
public void SetRoundSum ( int roundSum) {
ruler. roundSum = roundSum;
}
public void SetPlayDiskModeToPhysis ( bool isPhysis) {
if ( isPhysis) {
actionManager = Singleton< PhysisActionManager> . Instance as IActionManager ;
}
else {
actionManager = Singleton< CCActionManager> . Instance as IActionManager ;
}
}
public void LaunchDisk ( ) {
int [ ] beginPosY = new int [ 4 ] { 0 , 0 , 0 , 0 } ;
for ( int i = 0 ; i < ruler. roundDisksNum[ ruler. trialNum] ; ++ i) {
int randomNum = Random. Range ( 0 , 3 ) + 1 ;
ruler. speed = randomNum * ( ruler. roundNum + 4 ) ;
randomNum = Random. Range ( 0 , 3 ) + 1 ;
if ( randomNum == 1 ) {
ruler. color = "red" ;
}
else if ( randomNum == 2 ) {
ruler. color = "green" ;
}
else {
ruler. color = "blue" ;
}
ruler. size = Random. Range ( 0 , 3 ) + 1 ;
randomNum = Random. Range ( 0 , 2 ) ;
if ( randomNum == 1 ) {
ruler. direction = new Vector3 ( 3 , 1 , 0 ) ;
}
else {
ruler. direction = new Vector3 ( - 3 , 1 , 0 ) ;
}
do {
randomNum = Random. Range ( 0 , 4 ) ;
} while ( beginPosY[ randomNum] != 0 ) ;
beginPosY[ randomNum] = 1 ;
ruler. beginPos = new Vector3 ( - ruler. direction. x * 4 , - 0.5f * randomNum, 0 ) ;
GameObject disk = Singleton< DiskFactory> . Instance. GetDisk ( ruler) ;
actionManager. PlayDisk ( disk, ruler. speed, ruler. direction) ;
}
}
public void FreeFactoryDisk ( GameObject disk) {
Singleton< DiskFactory> . Instance. FreeDisk ( disk) ;
}
public void FreeAllFactoryDisk ( ) {
GameObject[ ] obj = FindObjectsOfType ( typeof ( GameObject ) ) as GameObject[ ] ;
foreach ( GameObject g in obj) {
if ( g. gameObject. name == "Disk(Clone)(Clone)" ) {
Singleton< DiskFactory> . Instance. FreeDisk ( g) ;
}
}
}
void Update ( ) {
if ( mainController. GetGameState ( ) == ( int ) GameState. Playing) {
ruler. sendTime += Time. deltaTime;
if ( ruler. sendTime > 2 ) {
ruler. sendTime = 0 ;
if ( ruler. roundSum == - 1 || ruler. roundNum < ruler. roundSum) {
mainController. SetViewTip ( "" ) ;
LaunchDisk ( ) ;
ruler. trialNum++ ;
if ( ruler. trialNum == 10 ) {
ruler. trialNum = 0 ;
ruler. roundNum++ ;
generateRoundDisksNum ( ) ;
}
}
else {
mainController. SetViewTip ( "Click Restart and Play Again!" ) ;
mainController. SetGameState ( ( int ) GameState. GameOver) ;
}
if ( ruler. trialNum == 0 ) mainController. SetViewRoundNum ( ruler. roundNum) ;
else mainController. SetViewRoundNum ( ruler. roundNum + 1 ) ;
mainController. SetViewTrialNum ( ruler. trialNum) ;
}
}
}
}
IActionManager.cs
是飞碟运动类型动作的管理接口,具体函数在运动学和物理学的两个适配器中被实现
public interface IActionManager {
void PlayDisk ( GameObject disk, float speed, Vector3 direction) ;
}
ISSActionCallback.cs
是飞碟运动完成后的回调接口,在飞碟运动完后调用,用以飞碟对象的销毁,具体函数在运动学和物理学的两个适配器中被实现
public enum SSActionEventType : int { Started, Competed }
public interface ISSActionCallback {
void SSActionEvent ( SSAction source,
SSActionEventType events = SSActionEventType. Competed,
int intParam = 0 ,
string strParam = null ,
Object objectParam = null ) ;
}
CCActionManager.cs
是飞碟运动学运动动作的适配器类,用以实现飞碟的运动学动作的管理
public class CCActionManager : SSActionManager , ISSActionCallback , IActionManager {
CCPlayDiskAction PlayDiskAction;
public void PlayDisk ( GameObject disk, float speed, Vector3 direction) {
PlayDiskAction = CCPlayDiskAction. GetSSAction ( direction, speed) ;
RunAction ( disk, PlayDiskAction, this ) ;
}
public void SSActionEvent ( SSAction source,
SSActionEventType events = SSActionEventType. Competed,
int intParam = 0 ,
string strParam = null ,
Object objectParam = null )
{
Singleton< RoundController> . Instance. FreeFactoryDisk ( source. gameObject) ;
}
}
PhysisActionManager.cs
是飞碟物理学运动动作的适配器类,用以实现飞碟的物理学动作的管理
public class PhysisActionManager : SSActionManager , ISSActionCallback , IActionManager {
PhysisPlayDiskAction PlayDiskAction;
public void PlayDisk ( GameObject disk, float speed, Vector3 direction) {
PlayDiskAction = PhysisPlayDiskAction. GetSSAction ( direction, speed) ;
RunAction ( disk, PlayDiskAction, this ) ;
}
public void SSActionEvent ( SSAction source,
SSActionEventType events = SSActionEventType. Competed,
int intParam = 0 ,
string strParam = null ,
Object objectParam = null )
{
Singleton< RoundController> . Instance. FreeFactoryDisk ( source. gameObject) ;
}
}
SSAction.cs
是动作基类,用以定义一个动作
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
public class SSAction : ScriptableObject {
public bool enable = true ;
public bool destroy = false ;
public GameObject gameObject { get ; set ; }
public Transform transform { get ; set ; }
public ISSActionCallback callback { get ; set ; }
public virtual void Start ( ) { }
public virtual void Update ( ) { }
}
SSActionManager.cs
是动作管理者的基类,用以定义一连串的动作应该怎么被管理
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
public class SSActionManager : MonoBehaviour {
private Dictionary< int , SSAction> actions = new Dictionary< int , SSAction> ( ) ;
private List< SSAction> waitingAdd = new List< SSAction> ( ) ;
private List< int > waitingDelete = new List< int > ( ) ;
protected void Update ( ) {
foreach ( SSAction ac in waitingAdd) {
actions[ ac. GetInstanceID ( ) ] = ac;
}
waitingAdd. Clear ( ) ;
foreach ( KeyValuePair< int , SSAction> kv in actions) {
SSAction ac = kv. Value;
if ( ac. destroy) {
waitingDelete. Add ( ac. GetInstanceID ( ) ) ;
}
else if ( ac. enable) {
ac. Update ( ) ;
}
}
foreach ( int key in waitingDelete) {
SSAction ac = actions[ key] ;
actions. Remove ( key) ;
Destroy ( ac) ;
}
waitingDelete. Clear ( ) ;
}
public void RunAction ( GameObject gameObject, SSAction action, ISSActionCallback manager) {
action. gameObject = gameObject;
action. transform = gameObject. transform;
action. callback = manager;
waitingAdd. Add ( action) ;
action. Start ( ) ;
}
}
CCPlayDiskAction.cs
是具体实现了飞碟运动学运动动作的一个类
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
public class CCPlayDiskAction : SSAction {
float gravity;
float speed;
Vector3 direction;
float time;
public static CCPlayDiskAction GetSSAction ( Vector3 direction, float speed) {
CCPlayDiskAction action = ScriptableObject. CreateInstance < CCPlayDiskAction> ( ) ;
action. gravity = 9.8f ;
action. time = 0 ;
action. speed = speed;
action. direction = direction;
return action;
}
public override void Start ( ) {
gameObject. GetComponent < Rigidbody> ( ) . isKinematic = true ;
}
public override void Update ( ) {
time += Time. deltaTime;
transform. Translate ( Vector3. down * gravity * time * Time. deltaTime) ;
transform. Translate ( direction * speed * Time. deltaTime) ;
if ( this . transform. position. y < - 5 ) {
this . destroy = true ;
this . enable = false ;
this . callback. SSActionEvent ( this ) ;
}
}
}
PhysisPlayDiskAction.cs
是具体实现了飞碟物理学运动动作的一个类
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
public class PhysisPlayDiskAction : SSAction {
float speed;
Vector3 direction;
public static PhysisPlayDiskAction GetSSAction ( Vector3 direction, float speed) {
PhysisPlayDiskAction action = ScriptableObject. CreateInstance < PhysisPlayDiskAction> ( ) ;
action. speed = speed;
action. direction = direction;
return action;
}
public override void Start ( ) {
gameObject. GetComponent < Rigidbody> ( ) . isKinematic = false ;
gameObject. GetComponent < Rigidbody> ( ) . velocity = speed * direction;
}
public override void Update ( ) {
if ( this . transform. position. y < - 5 ) {
this . destroy = true ;
this . enable = false ;
this . callback. SSActionEvent ( this ) ;
}
}
}
View
MainController.cs
控制回合控制器以及游戏视图,回合控制器RoundController用以监测游戏事件,当有事件发生时,MainController通知游戏视图View在屏幕上显示出相应的视图内容
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
enum GameState {
Ready = 0 , Playing = 1 , GameOver = 2
} ;
public class MainController : MonoBehaviour {
private RoundController roundController;
private View view;
private int N = 1 ;
private int gameState;
public GUISkin gameSkin;
void Start ( ) {
Director. GetInstance ( ) . mainController = this ;
roundController = gameObject. AddComponent < RoundController> ( ) ;
view = gameObject. AddComponent < View> ( ) ;
gameState = ( int ) GameState. Ready;
view. gameSkin = gameSkin;
}
public int GetN ( ) {
return N;
}
public void Restart ( ) {
view. Init ( ) ;
roundController. Reset ( ) ;
}
public void SetGameState ( int state) {
gameState = state;
}
public int GetGameState ( ) {
return gameState;
}
public void ShowPage ( ) {
switch ( gameState) {
case 0 :
view. ShowHomePage ( ) ;
break ;
case 1 :
view. ShowGamePage ( ) ;
break ;
case 2 :
view. ShowRestart ( ) ;
break ;
}
}
public void SetRoundSum ( int roundSum) {
roundController. SetRoundSum ( roundSum) ;
}
public void SetPlayDiskModeToPhysis ( bool isPhysis) {
roundController. SetPlayDiskModeToPhysis ( isPhysis) ;
}
public void SetViewTip ( string tip) {
view. SetTip ( tip) ;
}
public void SetViewScore ( int score) {
view. SetScore ( score) ;
}
public void SetViewRoundNum ( int round) {
view. SetRoundNum ( round) ;
}
public void SetViewTrialNum ( int trial) {
view. SetTrialNum ( trial) ;
}
public void Hit ( Vector3 position) {
Camera camera = Camera. main;
Ray ray = camera. ScreenPointToRay ( position) ;
RaycastHit[ ] hits;
hits = Physics. RaycastAll ( ray) ;
for ( int i = 0 ; i < hits. Length; i++ ) {
RaycastHit hit = hits[ i] ;
if ( hit. collider. gameObject. GetComponent < DiskData> ( ) != null ) {
hit. collider. gameObject. transform. position = new Vector3 ( 0 , - 6 , 0 ) ;
roundController. Record ( hit. collider. gameObject. GetComponent < DiskData> ( ) ) ;
view. SetScore ( roundController. GetScores ( ) ) ;
}
}
}
public void FreeAllFactoryDisk ( ) {
roundController. FreeAllFactoryDisk ( ) ;
}
}
View.cs
受到MainController的控制,显示出相应的视图内容
using System. Collections ;
using System. Collections. Generic ;
using UnityEngine ;
using UnityEngine. UI ;
public class View : MonoBehaviour {
private MainController mainController;
private int score;
private string tip;
private string roundNum;
private string trialNum;
public GUISkin gameSkin;
void Start ( ) {
score = 0 ;
tip = "" ;
roundNum = "" ;
trialNum = "" ;
mainController = Director. GetInstance ( ) . mainController;
}
public void SetTip ( string tip) {
this . tip = tip;
}
public void SetScore ( int score) {
this . score = score;
}
public void SetRoundNum ( int round) {
roundNum = "Round: " + round;
}
public void SetTrialNum ( int trial) {
if ( trial == 0 ) trial = 10 ;
trialNum = "Trial: " + trial;
}
public void Init ( ) {
score = 0 ;
tip = "" ;
roundNum = "" ;
trialNum = "" ;
}
public void AddTitle ( ) {
GUIStyle titleStyle = new GUIStyle ( ) ;
titleStyle. normal. textColor = Color. black;
titleStyle. fontSize = 50 ;
GUI. Label ( new Rect ( Screen. width / 2 - 80 , 20 , 60 , 100 ) , "Hit UFO" , titleStyle) ;
}
public void AddChooseModeButton ( ) {
GUI. skin = gameSkin;
if ( GUI. Button ( new Rect ( 380 , 280 , 160 , 80 ) , "开始游戏\n(默认为" + mainController. GetN ( ) + "回合)" ) ) {
mainController. SetRoundSum ( mainController. GetN ( ) ) ;
mainController. Restart ( ) ;
mainController. SetGameState ( ( int ) GameState. Playing) ;
}
}
public void ShowHomePage ( ) {
AddChooseModeButton ( ) ;
}
public void AddActionModeButton ( ) {
GUI. skin = gameSkin;
if ( GUI. Button ( new Rect ( 10 , Screen. height - 100 , 110 , 40 ) , "运动学模式" ) ) {
mainController. FreeAllFactoryDisk ( ) ;
mainController. SetPlayDiskModeToPhysis ( false ) ;
}
if ( GUI. Button ( new Rect ( 10 , Screen. height - 50 , 110 , 40 ) , "物理模式" ) ) {
mainController. FreeAllFactoryDisk ( ) ;
mainController. SetPlayDiskModeToPhysis ( true ) ;
}
}
public void AddBackButton ( ) {
GUI. skin = gameSkin;
if ( GUI. Button ( new Rect ( 10 , 10 , 60 , 40 ) , "Back" ) ) {
mainController. FreeAllFactoryDisk ( ) ;
mainController. Restart ( ) ;
mainController. SetGameState ( ( int ) GameState. Ready) ;
}
}
public void AddGameLabel ( ) {
GUIStyle labelStyle = new GUIStyle ( ) ;
labelStyle. normal. textColor = Color. black;
labelStyle. fontSize = 30 ;
GUI. Label ( new Rect ( 620 , 10 , 100 , 50 ) , "Score: " + score, labelStyle) ;
GUI. Label ( new Rect ( 250 , 220 , 50 , 200 ) , tip, labelStyle) ;
GUI. Label ( new Rect ( 620 , 60 , 100 , 50 ) , roundNum, labelStyle) ;
GUI. Label ( new Rect ( 620 , 110 , 100 , 50 ) , trialNum, labelStyle) ;
}
public void AddRestartButton ( ) {
if ( GUI. Button ( new Rect ( 380 , 280 , 160 , 80 ) , "Restart" ) ) {
mainController. FreeAllFactoryDisk ( ) ;
mainController. Restart ( ) ;
mainController. SetGameState ( ( int ) GameState. Playing) ;
}
}
public void ShowGamePage ( ) {
AddGameLabel ( ) ;
AddBackButton ( ) ;
AddActionModeButton ( ) ;
if ( Input. GetButtonDown ( "Fire1" ) ) {
mainController. Hit ( Input. mousePosition) ;
}
}
public void ShowRestart ( ) {
ShowGamePage ( ) ;
AddRestartButton ( ) ;
}
void OnGUI ( ) {
AddTitle ( ) ;
mainController. ShowPage ( ) ;
}
}
参考内容
参考博客