3D游戏作业:与游戏世界交互
1、编写一个简单的鼠标打飞碟(Hit UFO)游戏
游戏内容要求:
- 游戏有 n 个 round,每个 round 都包括10 次 trial;
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
- 每个 trial 的飞碟有随机性,总体难度随 round 上升; 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
游戏的要求:
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
- 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
实现内容:
这次的编程作业主要是实现交互内容,其他的要求例如:物件的移动和MVC架构在之前的作业都实现过了,所以不太难,但是我这次采用的MVC架构是初始版MVC,并没有采用动作分离,因为我认为打飞碟这个游戏的动作本身就不是很多,没有必要采用动作分离架构。
游戏界面如下:
游戏可以实现暂停和重新开始功能,游戏总共有3个round,难度依次递增。每个round有10个trial(我理解的trial就是生成飞碟。。 不知道对不对),有三种飞碟分别对应不同的分值,分值最大的飞碟体积更小,难度更高。每个飞碟的碰撞体积只有一个,为了提升性能,我用了长方体碰撞体积而不是完全仿照飞碟形状,这样做虽然会略失判断精度,但可以提升性能。
游戏第三个Round结束后会跳到第四个Round,但此时游戏已经终止,只能重新开始。
生成飞碟的工厂我遵循了单例模式:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sing : MonoBehaviour
{
protected static Factory instance;
public static Factory Instance{
get{
if(instance == null){
instance = (Factory)FindObjectOfType(typeof(Factory));
// Debug.Log("asdaszzzz");
}
return instance;
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
public class Factory : MonoBehaviour
{
public List<GameObject> UFO_on = new List<GameObject>();
public List<GameObject> UFO_off = new List<GameObject>();
public void genUFO() //借鉴了网上的博客
{
// Debug.Log("gen!");
GameObject ufo;
if(UFO_off.Count == 0)
{
float num = Random.Range(0f,9f);
if(num > 6)
ufo = Instantiate(Resources.Load("Prefabs/ufo1"), Vector3.zero, Quaternion.identity) as GameObject;
else if(num > 3)
ufo = Instantiate(Resources.Load("Prefabs/ufo2"), Vector3.zero, Quaternion.identity) as GameObject;
else
ufo = Instantiate(Resources.Load("Prefabs/ufo3"), Vector3.zero, Quaternion.identity) as GameObject;
}
else
{
ufo = UFO_off[0];
UFO_off.RemoveAt(0);
}
float x = Random.Range(-8f,8f);
float y = Random.Range(-5f,5f);
float z = Random.Range(-2f,2f);
ufo.transform.position = new Vector3(x, y ,0);
ufo.transform.Rotate(new Vector3(x < 0 ? -x*60 : x*60, y < 0 ? -y*60:y*60, z<0?-z*20:z*20));
UFO_on.Add(ufo);
}
public void recycleUFO(GameObject obj)
{
for(int i=0; i < UFO_on.Count ;i++){
if(UFO_on.ToArray()[i] == obj){
UFO_on.RemoveAt(i);
}
}
obj.transform.position = new Vector3(100,100,100);
UFO_off.Add(obj);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
MVC架构如下:
Controller:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Controller : System.Object
{
private static Controller _instance;
public Model currentModel {get; set;}
public bool running{get; set;}
public static Controller getInstance(){
if(_instance == null){
_instance = new Controller();
}
return _instance;
}
public int getFPS(){
return Application.targetFrameRate;
}
public void setFPS(int fps){
Application.targetFrameRate = fps;
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
View:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface IUserAction
{
void GameOver();
}
public class View : MonoBehaviour
{
private Model action;
private GUIStyle fontstyle = new GUIStyle();
private GUIStyle fontstyle1 = new GUIStyle();
// Start is called before the first frame update
void Start()
{
action = Controller.getInstance().currentModel as Model;
fontstyle.fontSize = 30;
fontstyle.normal.textColor = Color.yellow;
fontstyle1.fontSize = 30;
fontstyle1.normal.textColor = Color.yellow;
}
// Update is called once per frame
void Update()
{
}
public void OnGUI(){
GUI.Label(new Rect(10,40,200,200),"Score: " + action.score, fontstyle);
GUI.Label(new Rect(10,10,200,200),"Round: " + action.round, fontstyle);
GUI.Label(new Rect(10,70,200,200),"Trial: " + action.trial, fontstyle);
if (GUI.Button(new Rect(10, 110, 100, 50), "Pause"))
{
action.Pause();
}
// if (GUI.Button(new Rect(10, 170, 100, 50), "Start"))
// {
// action.Start();
// }
if (GUI.Button(new Rect(10, 170, 100, 50), "Restart"))
{
action.Restart();
}
}
}
Model:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public interface ForModel{
void LoadResources();
void Pause();
void Resume();
}
public class Model : MonoBehaviour, ForModel
{
public int score;
public int round;
public int mytime;
public int trial;
Controller controller;
public Factory myfactory;
void Awake(){
controller = Controller.getInstance();
controller.setFPS(60);
controller.currentModel = this;
controller.running=true;
LoadResources();
score=0;
round=1;
trial=0;
mytime=0;
myfactory=Sing.Instance;
InvokeRepeating("updatetime",1f,1f);
InvokeRepeating("gentiral",1f,3f);
}
public void Start(){
Debug.Log(myfactory);
for(int i=0; i<10;i++){
myfactory.genUFO();
}
}
public void Restart(){
controller.running=false;
score=0;
round=1;
trial=0;
mytime=0;
List<GameObject> ufos = myfactory.UFO_on;
List<GameObject> t= new List<GameObject>();
foreach(GameObject ufo in ufos){
t.Add(ufo);
}
ufos.Clear();
foreach(GameObject st in t){
myfactory.recycleUFO(st);
}
for(int i=0; i<10;i++){
myfactory.genUFO();
}
controller.running=true;
}
public void Resume(){
}
public void Pause(){
controller.running=!controller.running;
}
public void LoadResources(){
}
public void gentiral(){
if(controller.running == false)
return;
trial+=1;
while (myfactory.UFO_on.Count < 10){
myfactory.genUFO();
}
}
public void updatetime(){
if(controller.running == false)
return;
mytime+=1;
if(mytime>=30){
mytime=0;
trial=0;
round+=1;
}
Debug.Log(mytime);
}
// Start is called before the first frame update
// Update is called once per frame
void Update()
{
if(controller.running == false)
return;
List<GameObject> ufos = myfactory.UFO_on;
try{
foreach(GameObject ufo in ufos)
{
Vector3 v = new Vector3(ufo.transform.localRotation.x,ufo.transform.localRotation.y,ufo.transform.localRotation.z);
ufo.transform.Translate(v*Time.deltaTime*round*2);
if(ufo.transform.position.x > 10 || ufo.transform.position.x < -10){
myfactory.recycleUFO(ufo);
}
if(ufo.transform.position.y > 10 || ufo.transform.position.y < -10){
myfactory.recycleUFO(ufo);
}
if(ufo.transform.position.z < -10){
myfactory.recycleUFO(ufo);
}
}
}
catch{
}
if (Input.GetButtonDown("Fire1")) {
// Debug.Log ("Fired Pressed");
Debug.Log (Input.mousePosition);
Vector3 mp = Input.mousePosition; //get Screen Position
//create ray, origin is camera, and direction to mousepoint
Camera ca;
ca = Camera.main;
Ray ray = ca.ScreenPointToRay(Input.mousePosition);
//Return the ray's hits
RaycastHit[] hits = Physics.RaycastAll (ray);
foreach (RaycastHit hit in hits) {
if(hit.transform.gameObject == null){
continue;
}
if(hit.transform.gameObject.name == "ufo1(Clone)"){
score += 1;
}
if(hit.transform.gameObject.name == "ufo2(Clone)"){
score += 2;
}
if(hit.transform.gameObject.name == "ufo3(Clone)"){
score += 3;
}
print (hit.transform.gameObject.name);
if (hit.collider.gameObject.tag.Contains("Finish")) { //plane tag
// Debug.Log ("hit " + hit.collider.gameObject.name +"!" );
}
myfactory.recycleUFO(hit.transform.gameObject);
Debug.Log("on:"+myfactory.UFO_on.Count);
Debug.Log("off"+myfactory.UFO_off.Count);
// Destroy (hit.transform.gameObject.transform.parent.gameObject);
}
}
}
}
以上则是打飞碟游戏的实现,完整代码见github
2、编写一个简单的自定义 Component (选做)
用自定义组件定义几种飞碟,做成预制
参考官方脚本手册 https://docs.unity3d.com/ScriptReference/Editor.html
实现自定义组件,编辑并赋予飞碟一些属性
我做了三种飞碟component,它们的预制资源都在Resources/Prefabs中