游戏设计模式-重访设计模式
读《游戏设计模式》第一部分的总结-代码没运行只是提示模式的构建方法
命令模式
特点:Character根据情况获取相应Command,Command命令Character执行方法。
场景:Character的方法和情况判断需要解耦。多种情况会在多种Character的方法中进行判断
注意:真正的运行方法都是在Character下的,Command可以简化为委托
public interface Command{
public void execute(Character go);//这里使用游戏对象基类(能检测输入的基类)更好
}
public class JumpCommand implements Command{
public void execute(Character go){
go.jump();
}
}
public class RunCommand implements Command{
public void execute(Character go){
go.run();
}
}
public class InputCommands{
RunCommand run=new RunCommand();
JumpCommand jump=new JumpCommand();
public Command getInput(){
if(Input.GetKeyDown("W"))return run;
else if(Input.GetKeyDown("Space") return jump;
else return null;
}
}
public class Character{
void Update(){
Command command=InputCommands.getInput();
if(command!=null)command.excute(this);
}
void run(){}
void jump(){}
}
观察者模式
特点:被观察者注册观察者,需要通知时向所有观察者通知信息
场景:两种不同功能的模块(之间只有事件通知关系)需要也应该解耦成观察者和被观察者,不混杂在一起
注意:
- 两个模块都有各自的作用,和命令模式相比,命令可以简化成委托,为Character服务;但观察者只是和被观察者有事件上的耦合。
- 如果被观察者不销毁而观察者已经不再使用,需要手动去销毁观察者(因为被观察者始终持有引用)
public class Observer{
private beNotified(){
}
}
public class BeObserved{
private ArrayList<Observer> obs=new ArrayList<Observer>();
private notify(){
for(int i=0;i<obs.length;i++){
obs.get(i).beNotified();
}
}
}
原型模式
特点:通过现有的对象克隆出相同的对象,克隆结束后两者之间是相互独立的
场景:当类的实例化比较复杂且比较相似时使用
注意:和享元模式相比,各个个体之间是没有联系的,数据也是相互独立的
public abstract class Shape implements Cloneable{
@Override
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
public class Circle extends Shape{}
public class Rectangle extends Shape{}
享元模式
特点:大量对象持有一个共同的引用,所以这个引用只被实例化一次
场景:需要创建大量重复的对象,同时这些对象的部分数据是不变的或是分成几类的
注意:共同的引用其状态不应该经常被改变
//字段不变
public class TreeModel{//每个树的材质、纹理、叶子等都相同
Mesh mesh;
Texture bark;
Texture leaves;
}
public class Tree
{
TreeModel model_;
//以下的字段每个对象都不同
Vector position_;
double height_;
double thickness_;
Color barkTint_;
Color leafTint_;
};
//字段枚举
public class Terrain{
int getMovementCost() const { return movementCost_; }
bool isWater() const { return isWater_; }
const Texture getTexture() const { return texture_; }
int movementCost_;
bool isWater_;
Texture texture_;
}
public class World{
Terrain grassTerrain=new Terrain(XXXXX);
Terrain blockTerrain=new Terrain(XXXXX);
....
Terrain[] terrains=new Terrain[10[10];//之后让terrains指向不同的XXXTerrain
}
状态模式
特点:通过管理对象的状态来定义对象的行为,通常需要画状态机
场景:对象的行为基于一些内在状态,状态可以被严格地分割为相对较少的不相干项目。而对象需要响应各种输入(或其他条件判断)
注意:当状态管理过于复杂(制作更强大的AI时)使用行为树和规划系统
//简单的情况下可以使用枚举来管理对象的状态
enum State
{
STATE_STANDING,
STATE_JUMPING,
STATE_DUCKING,
STATE_DIVING
}
//复杂的情况使用状态机
interface BasicState{
public BasicState handleInput(Heroine heroine, Input input);
public void update(Heroine heroine);
//入口方法,当进入该状态时需要调用,相对的也可以定义出口方法。
public void enter(Heroine heroine);
}
//如果你的State都不需要有其他的变量(比如用来计时等)则简化为委托(但一般都有其他变量)
class StandState implements BasicState{
public BasicState handleInput(Heroine heroine, Input input) {
if (input == PRESS_A){
return new JumpState();
}
}
public void update(Heroine heroine){
//dosomething
}
public void enter(Heroine heroine){
heroine.setGraphics(IMAGE_STAND);
}
}
class JumpState implements BasicState{
public BasicState handleInput(Heroine heroine, Input input) {
if (input == PRESS_DOWN){
return new OtherState();
}
}
public void update(Heroine heroine){
//dosomething
}
public void enter(Heroine heroine){
heroine.setGraphics(IMAGE_JUMP);
}
}
class OtherState implements BasicState{//...}
class Heroine{
//如果状态使用者是一个单例(不是单例模式)可以使用静态参数来使用状态
BasicState state=new StandState();
void handleInput(Input input){
BasicState newState = state.handleInput(this, input);
if (newState != NULL){
Destroy(state);//清理工作
state = newState;
state.enter(this)
}
}
void update(){
state.update(this);
}
}
并发状态
当状态之间具有叠加关系时,可以在Heroine再定义一个state来指向叠加状态。但要注意叠加的越多,状态之间if判断越复杂。比如持枪状态和未持枪状态下的各种跳跃、行走
分层状态
当状态之间具有相似关系时,可以在使用继承来模拟状态的分层。子状态可以调用父状态的handleInput和update,不同子状态又有各自的处理,减少代码
下推自动机
当Heroine需要记住上一状态等操作时,可以维护一个容器(比如栈)来存储状态
单例模式
特点:类有且只有一个实例化对象,是可以被全局访问的,通常是惰性初始化
场景:创建多个对象会出错的类、文件读写类、需要全局访问的类
注意:尽可能的少使用单例模式
缺点:
- 全局变量将会使代码变得不可维护
- 过渡滥用单例模式,单例模式解决两个问题(单例和全局),但实际上只需要解决一个问题
- 对并行不友好
- 惰性初始化在游戏开发中是不可控的因素
解决: - 可以使用静态类解决惰性初始化
- 观察是否真的需要单例模式,XXXManager往往可以在XXX类执行,而不需要通过一个特定、单独的管理器管理
- 如果是为了实现单例,可以在多次创建时抛出异常等方法控制。让该类只有一个非全局的示例。被正确销毁后允许创建下一个实例
- 如果是为了获取访问,可以考虑在方法里传参、在基类里定义保护对象、加入到已有的全局变量里、从服务器定位中获得