🔍目的
允許編碼行為作為虛擬機的指令
🔍解釋
真實世界例子
一個團隊正在開發一款新的巫師對戰游戲。巫師的行為需要經過精心的調整和上百次的游玩測試。每次當游戲設計師想改變巫師行為時都讓程序員去修改代碼這是不妥的,所以巫師行為以數據驅動的虛擬機方式實現。
通俗描述
字節碼模式支持由數據而不是代碼驅動的行為。
維基百科
指令集定義了可以執行的低級操作。一系列指令被編碼為字節序列。虛擬機一次一條地執行這些指令,中間的值用棧處理。通過組合指令,可以定義復雜的高級行為。
程序示例
創建游戲對象 巫師 類
@AllArgsConstructor
@Setter
@Getter
@Slf4j
public class Wizard {private int health;private int agility;private int wisdom;private int numberOfPlayedSounds;private int numberOfSpawnedParticles;public void playSound() {LOGGER.info("Playing sound");numberOfPlayedSounds++;}public void spawnParticles() {LOGGER.info("Spawning particles");numberOfSpawnedParticles++;}
}
?展示虛擬機可用的指令。每個指令對于如何操作棧中的數據都有自己的語義。例如,增加指令,其取得棧頂的兩個元素并把結果壓入棧中。
@AllArgsConstructor
@Getter
public enum Instruction {LITERAL(1), // e.g. "LITERAL 0", push 0 to stackSET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set healthSET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdomSET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agilityPLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play soundSPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particlesGET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's healthGET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agilityGET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdomADD(10), // e.g. "ADD", pop 2 values, push their sumDIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division// ...
}
創建核心類? 虛擬機 類 。?它將指令作為輸入并執行它們以提供游戲對象行為。
@Getter
@Slf4j
public class VirtualMachine {private final Stack<Integer> stack = new Stack<>();private final Wizard[] wizards = new Wizard[2];public VirtualMachine() {wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),0, 0);wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32),0, 0);}public VirtualMachine(Wizard wizard1, Wizard wizard2) {wizards[0] = wizard1;wizards[1] = wizard2;}public void execute(int[] bytecode) {for (var i = 0; i < bytecode.length; i++) {Instruction instruction = Instruction.getInstruction(bytecode[i]);switch (instruction) {case LITERAL:// Read the next byte from the bytecode.int value = bytecode[++i];// Push the next value to stackstack.push(value);break;case SET_AGILITY:var amount = stack.pop();var wizard = stack.pop();setAgility(wizard, amount);break;case SET_WISDOM:amount = stack.pop();wizard = stack.pop();setWisdom(wizard, amount);break;case SET_HEALTH:amount = stack.pop();wizard = stack.pop();setHealth(wizard, amount);break;case GET_HEALTH:wizard = stack.pop();stack.push(getHealth(wizard));break;case GET_AGILITY:wizard = stack.pop();stack.push(getAgility(wizard));break;case GET_WISDOM:wizard = stack.pop();stack.push(getWisdom(wizard));break;case ADD:var a = stack.pop();var b = stack.pop();stack.push(a + b);break;case DIVIDE:a = stack.pop();b = stack.pop();stack.push(b / a);break;case PLAY_SOUND:wizard = stack.pop();getWizards()[wizard].playSound();break;case SPAWN_PARTICLES:wizard = stack.pop();getWizards()[wizard].spawnParticles();break;default:throw new IllegalArgumentException("Invalid instruction value");}LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack());}}public void setHealth(int wizard, int amount) {wizards[wizard].setHealth(amount);}// other setters ->// ...
}
展示虛擬機完整示例
?
public static void main(String[] args) {var vm = new VirtualMachine(new Wizard(45, 7, 11, 0, 0),new Wizard(36, 18, 8, 0, 0));vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH"));vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY"));vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0"));vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM"));vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2"));vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE"));vm.execute(InstructionConverterUtil.convertToByteCode("ADD"));vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH"));}
控制臺輸出
?
16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0]
16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18]
16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2]
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9]
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54]
16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains []
🔍類圖
Bytecode class diagram?
🔍適用場景
當您需要定義很多行為并且游戲的實現語言不合適時,請使用字節碼模式,因為:
- 它的等級太低,使得編程變得乏味或容易出錯。
- 由于編譯時間慢或其他工具問題,迭代它需要很長時間。
- 它有太多的信任。 如果您想確保定義的行為不會破壞游戲,您需要將其與代碼庫的其余部分進行沙箱化。