3D游戏编程设计作业五

本文介绍了如何使用Adapter模式改进3D游戏,包括改进飞碟游戏以同时支持物理运动和运动学运动,以及设计打靶游戏。在飞碟游戏中,创建了统一的IActionManager接口,使不同运动控制器能协同工作。打靶游戏增加了风向和强度,箭射中后有颤抖效果,提高了游戏难度。所有代码已上传至github。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、改进飞碟(Hit UFO)游戏:

游戏内容要求

  1. 按 adapter模式 设计图修改飞碟游戏
  2. 使它同时支持物理运动与运动学(变换)运动

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、打靶游戏(可选作业):

  • 游戏内容要求:

    1. 靶对象为 5 环,按环计分;
    2. 箭对象,射中后要插在靶上
      • 增强要求:射中后,箭对象产生颤抖效果,到下一次射击 或 1秒以后
    3. 游戏仅一轮,无限 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值