1、改进飞碟(Hit UFO)游戏:
游戏内容要求
- 按 adapter模式 设计图修改飞碟游戏
- 使它同时支持物理运动与运动学(变换)运动
Adapter模式简介
Adapter模式就是将一个类的接口转换为客户希望的另一个接口。该模式可以使得原本由于接口不兼容不能一起工作的可以一起工作。本次作业的实现要同时支持物理运动和运动学运动,故需要为两个运动控制器CCActionManager和PhysisActionManager类创建一个统一的接口作为Adapter。
代码改变
游戏规则更新
本次游戏的大多数代码沿用了上次的代码,为了让游戏的可玩性更强,我首先在原先的基础上改变了规则,新更新的规则如下:
1.飞碟颜色共有黄、黑、红三种,黄色2分,黑色3分,红色5分,黑色飞碟在round2后才会出现,红色飞碟在round3后才会出现
改变DiskFactory代码如下:
if(random_num > 6 && round >= 3){
_score = 5;
random_start = new Vector3(Random.Range(-80, -90), Random.Range(30, 70), 0);
_color = Color.red;
}
else if(random_num <= 6 && random_num > 3 && round >= 2){
_score = 3;
random_start = new Vector3(Random.Range(-80, -100), Random.Range(30,70), 0);
_color = Color.black;
}
else {
_score = 2;
random_start = new Vector3(Random.Range(-80, -120), Random.Range(30,90), 0);
_color = Color.yellow;
}
2.增加生命值设定,如果飞碟飞出视野,在UserGUI类中扣除生命值,生命值为0游戏结束
public void ReduceBlood(){
if(blood > 0)
blood--;
}
新增代码
新增IActionManager接口作为Adapter接口,原先存在的运动学运动管理器CCActionManager类和新增的物理学运动管理器PhysicalActionManager都需要实现这个接口,这样场记只需要调用该接口使飞碟运动就实现了同时兼容两种运动,该接口只需要需要实现飞碟运动的方法,即上次代码中UFOfly的方法,代码如下
public interface IActionManager{
void UFOfly(GameObject disk);
}
修改CCActionManager类让其继承适配器接口
public class CCFlyActionManager : SSActionManger,ISSActionCallback,IActionManager {
public SSActionEventType Complete = SSActionEventType.Competeted;
public void SSActionEvent(SSAction source) {
Complete = SSActionEventType.Competeted;
source.gameObject.SetActive(false);
}
public void UFOfly(GameObject disk){
Complete = SSActionEventType.Started;
CCFlyAction action = CCFlyAction.getAction(disk.GetComponent<Disk>().Speed);
addAction(disk, action, this);//激活对象,添加动作
}
}
仿照CCFlyAction添加物理运动类PhysicalAction,对于物理运动,需要给游戏对象添加刚体,刚体添加后物体会自动下坠,相当于重力效果
public class PhysicalAction : SSAction{
public float speedx;
private float gravity = 10f;
// Use this for initialization
public override void Start(){
//添加刚体
if (!this.gameObject.GetComponent<Rigidbody>()){
this.gameObject.AddComponent<Rigidbody>();
}
this.gameObject.GetComponent<Rigidbody>().AddForce(Vector3.up * gravity * 0.5f, ForceMode.Acceleration);
this.gameObject.GetComponent<Rigidbody>().AddForce(new Vector3(speedx, 0, 0), ForceMode.VelocityChange);
}
private PhysicalAction(){}
public static PhysicalAction getAction(float speed){
PhysicalAction action = CreateInstance<PhysicalAction>();
action.speedx = speed;
return action;
}
// Update is called once per frame
override public void Update(){
if (transform.position.y <= -8)
{
Destroy(this.gameObject.GetComponent<Rigidbody>());//移除刚体
destory = true;
callback.SSActionEvent(this);//反馈
}
}
}
PhysicalActionManager类的实现和运动学运动管理器基本一致,只是需要使用的action调整为物理运动
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using myinterface;
public class PhysicalActionManager : SSActionManger,ISSActionCallback,IActionManager {
public SSActionEventType Complete = SSActionEventType.Competeted;
public void SSActionEvent(SSAction source) {
Complete = SSActionEventType.Competeted;
source.gameObject.SetActive(false);
}
public void UFOfly(GameObject disk){
Complete = SSActionEventType.Started;
PhysicalAction action = PhysicalAction.getAction(disk.GetComponent<Disk>().Speed);
addAction(disk, action, this);//激活对象,添加动作
}
}
首先需要修改FirstController的类型为适配器接口,以达到适配器模式的效果
public IActionManager fly_manager1,fly_manager2;
为UserAction接口增加一个方法用于动作模式的选择
public void chooseAction(int choose){
action_type = choose;
}
更新UserGUI用于动作模式选择
if(!is_play){
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2-40, 200, 70), "CCAction",buttonStyle)){
action.chooseAction(1);
action.reStart();
is_play = true;
return;
}
if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2+40, 200, 70), "PhysicalAction",buttonStyle)){
action.chooseAction(0);
action.reStart();
is_play = true;
return;
}
}
FirstController根据选择的模式控制飞碟运动模式
GameObject d = DF.GetDisk(round);
if(action_type == 0){
fly_manager2.UFOfly(d);
}
else{
fly_manager1.UFOfly(d);
}
运行截图如下
完整代码见github
2、打靶游戏(可选作业):
-
游戏内容要求:
- 靶对象为 5 环,按环计分;
- 箭对象,射中后要插在靶上
- 增强要求:射中后,箭对象产生颤抖效果,到下一次射击 或 1秒以后
- 游戏仅一轮,无限 trials;
- 增强要求:添加一个风向和强度标志,提高难度
代码实现
整体代码框架如下,部分代码复用了打飞碟中的代码
箭靶对象
箭靶效果图如下
通过5个圆柱体添加箭靶对象,即首先生成一个空的游戏对象,给它添加5个子对象圆柱体并添加不同的颜色材料,给5个圆柱体添加碰撞器Mesh Collider,并勾选is Trigger作为触发器。
箭靶对象需要挂载一个Target.cs脚本,用于检测箭是否射中箭靶,即需要给箭对象添加刚体和碰撞器,带有触发器的对象接收到带碰撞器的对象会触发OnTriggerEnter函数,该脚本还有一个功能是实现分数叠加,这里我用圆柱体的名称计算分数
public class Target : MonoBehaviour {
public int target_score;//环数
public CCArrowAction arrowshoot;
public FirstController controller;
public void Start() {
controller = (FirstController)SSDirector.getInstance().currentScenceController;
}
void OnTriggerEnter(Collider arrow) {
if (arrow.gameObject.tag == "Arrow") {
if (!arrow.gameObject.GetComponent<Arrow>().hit) {
arrow.gameObject.GetComponent<Arrow>().hit = true;
target_score = (name[0] - '0');//通过名称控制分数
controller.score += target_score;
}
arrowshoot = (CCArrowAction)arrow.gameObject.GetComponent<Arrow>().action;
arrow.gameObject.GetComponent<Rigidbody>().velocity = Vector3.zero;//箭插入箭靶效果
arrowshoot.Destory();
}
}
}
箭对象和弓对象
箭和弓我从网上下载了预设,箭需要添加碰撞器和刚体,并挂载Arrow.cs脚本
Arrow.cs脚本用于添加箭的动作及检测是否射中箭靶
public class Arrow : MonoBehaviour {
public bool hit = false;
public SSAction action;
}
ArrowFactory作为工厂模式的实现用于箭的生产和回收,实现和打飞碟基本一致,这里需要注意回收的箭需要满足没有射中箭靶且飞出了一定范围,在工厂中给箭添加Arrow.cs脚本
public class ArrowFactory : MonoBehaviour {
private List<GameObject> used;//正在使用的箭
private List<GameObject> free;//未激活的箭
public static ArrowFactory AF;
//public FirstController controller;
public GameObject arrowPrefab = null;
void Awake(){
if(AF == null){
used = new List<GameObject>();
free = new List<GameObject>();
AF = Singleton<ArrowFactory>.Instance;
}
}
void Start () {
//controller = SSDirector.getInstance().currentScenceController as FirstController;
//controller.AF = this;
arrowPrefab = Instantiate(Resources.Load("prefabs/Arrow")) as GameObject;
arrowPrefab.SetActive(false);
arrowPrefab.transform.localEulerAngles = new Vector3(0, 90, 0);
//free.Add(controller.arrow);
}
public GameObject getArrow(Vector3 pos) {
freeArrow();
GameObject arrow = null;
if (free.Count == 0) {
arrow = Instantiate(Resources.Load<GameObject>("Prefabs/Arrow"));
}
else {
arrow = free[0];
free.Remove(free[0]);
}
arrow.SetActive(true);
used.Add(arrow);
arrow.transform.position = pos;
arrow.transform.localEulerAngles = arrowPrefab.transform.localEulerAngles;
if(arrow.GetComponent<Arrow>() == null){
arrow.AddComponent<Arrow>();
}
return arrow;
}
//回收箭
public void freeArrow(){
foreach (GameObject x in used){
if ((!x.gameObject.activeSelf) || (!x.GetComponent<Arrow>().hit && x.transform.position.z > 20)){
free.Add(x);
used.Remove(x);
return;
}
}
}
}
CCArrowAction控制箭的飞行动作,给箭添加一个z方向上向前的力并添加风力,我这里风力只考虑了x方向的风力随机性,y方向和z方向为定值,也可以设置为随机值,主要看代码的实现方式,风力是一个持续的力,我在Update中实现,如果箭没有射中并飞出边界需要销毁箭对象
public class CCArrowAction : SSAction {
Vector3 force;
public float wind;
public static CCArrowAction GetSSAction(){
CCArrowAction action = ScriptableObject.CreateInstance<CCArrowAction>();
return action;
}
// Use this for initialization
public override void Start(){
force = new Vector3(0, 0, 10);
//添加刚体
gameObject.GetComponent<Rigidbody>().velocity = Vector3.zero;
gameObject.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse);
}
// Update is called once per frame
public override void Update(){
wind = Random.Range(-5f,5f);//添加风力
//风的力持续作用在箭身上
this.gameObject.GetComponent<Rigidbody>().AddForce(new Vector3(wind,0.2f,2), ForceMode.Force);
//检测是否被击中或是超出边界
if (this.transform.position.z > 20 && this.gameObject.GetComponent<Arrow>().hit == false){
this.destory = true;
this.callback.SSActionEvent(this);
Destroy(gameObject.GetComponent<BoxCollider>());
}
}
public void Destory(){
this.destory = true;
this.callback.SSActionEvent(this);
Destroy(gameObject.GetComponent<BoxCollider>());
}
}
CCArrowActionManager对箭的飞行进行管理,和上次代码实现类似
public class CCArrowActionManager : SSActionManger,ISSActionCallback,IActionManager {
public float wind = 0;
protected new void Update(){
base.Update();
}
public void SSActionEvent(SSAction source) {
source.gameObject.SetActive(false);
source.gameObject.GetComponent<Arrow>().hit = false;
}
public void Arrowfly(GameObject arrow,Vector3 pos){
arrow.transform.parent = null;
CCArrowAction action = CCArrowAction.GetSSAction();
arrow.transform.position = pos;
this.addAction(arrow,action,this);
arrow.GetComponent<Arrow>().action = action;
wind = Random.Range(-5f,5f);
}
public float getWind(){
return wind;
}
}
弓对象需要挂载一个BowMove的脚本,用于实现弓通过键盘控制位置,只需要根据摄像机的方向稍微更改课件上的代码即可
public class BowMove : MonoBehaviour {
public float speedX = 0.01f;
public float speedY = 0.01f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.Space))
{
this.transform.position = new Vector3(-0.02f, 0.95f, -4);
}
float translationY = Input.GetAxis("Vertical") * speedY;
float translationX = Input.GetAxis("Horizontal") * speedY;
translationY *= Time.deltaTime;
translationX *= Time.deltaTime;
transform.Translate(0, translationY, 0);
transform.Translate(translationX, 0, 0);
}
}
场记类FirstController控制刚开始预设的加载和位置判断,分数统计等,对于对象的位置,刚开始加载出的箭位置和弓需要一致,箭的transform.parent需要设置为弓的transform,才能保证箭和弓一起移动,当箭射出后将其transform.parent改为null即可保持其运动方向
public class FirstController : MonoBehaviour, ISceneController, UserAction{
public IActionManager actionManager;
public ArrowFactory AF;
public GameObject arrow;
public UserGUI user_gui;
public GameObject bow;
public bool start = false;
public int score = 0;
// Use this for initialization
void Start () {
start = true;
SSDirector director = SSDirector.getInstance();
director.currentScenceController = this;
director.currentScenceController.LoadResources();
user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
actionManager = gameObject.AddComponent<CCArrowActionManager>();
AF = gameObject.AddComponent<ArrowFactory>();
}
// Update is called once per frame
void Update () {
}
public void LoadResources(){
Instantiate(Resources.Load("prefabs/Target"));//加载箭靶
arrow = Instantiate(Resources.Load("prefabs/Arrow")) as GameObject;
bow = Instantiate(Resources.Load("prefabs/Bow")) as GameObject;//加载弓
arrow.transform.position = bow.transform.position;
arrow.transform.parent = bow.transform;
}
public void hit(Vector3 pos){
arrow = AF.getArrow(bow.transform.position);
arrow.transform.parent = bow.transform;
actionManager.Arrowfly(arrow, bow.transform.position);
}
public int getScore(){
return score;
}
public void reStart(){
score = 0;
start = true;
}
public void endGame(){
start = false;
}
public float getWind(){
return actionManager.getWind();
}
}
最后UserGUI用于分数显示和风力显示
public class UserGUI : MonoBehaviour {
public bool isButtonDown = false;//是否有箭发射
public Camera camera;
public UserAction action;
public ISceneController scene;//场景接口
void Start () {
GameObject obj = GameObject.Find("Main Camera");
camera = obj.GetComponent<Camera>();
action = SSDirector.getInstance().currentScenceController as UserAction;
scene = SSDirector.getInstance().currentScenceController as ISceneController;
}
// Update is called once per frame
void OnGUI () {
GUIStyle scoreStyle = new GUIStyle();
GUIStyle buttonStyle = new GUIStyle();
buttonStyle = new GUIStyle("Button");
buttonStyle.fontSize = 26;
scoreStyle.fontSize = 30;
scoreStyle.normal.textColor = Color.black;
float wind = scene.getWind();
GUI.Label(new Rect(Screen.width - 200, Screen.height / 2-180, 100, 50), "Score: " + action.getScore().ToString(),scoreStyle);
if(wind < 0)
GUI.Label(new Rect(Screen.width - 220, Screen.height / 2-150, 100, 50), "Wind left: " + string.Format("{0:N2}",wind.ToString()),scoreStyle);
else
GUI.Label(new Rect(Screen.width - 220, Screen.height / 2-150, 100, 50), "Wind right: " + wind.ToString(),scoreStyle);
if (GUI.Button(new Rect(Screen.width - 200, Screen.height / 2-100, 140, 70), "Restart",buttonStyle)){
action.reStart();
}
if (Input.GetMouseButtonDown(0) && !isButtonDown) {
Ray mouseRay = camera.ScreenPointToRay (Input.mousePosition);
action.hit(mouseRay.direction);
isButtonDown = true;
}
else if(Input.GetMouseButtonDown(0) && isButtonDown) {
isButtonDown = false;
}
}
}
代码中用到的各个接口,基本都是在打飞碟的基础上修改的
namespace myInterface {
public interface ISceneController{
void LoadResources();//加载场景
float getWind();//风力获取
}
public enum SSActionEventType : int { Started, Competeted}
//动作处理接口
public interface ISSActionCallback{
void SSActionEvent(SSAction source);
}
//箭飞行动作
public interface IActionManager{
void Arrowfly(GameObject arrow, Vector3 pos);
float getWind();
}
public interface UserAction{
void hit(Vector3 vec);
int getScore();//获得分数
void reStart();
void endGame();
}
}
运行效果如下:
代码见github