知识屋:更实用的电脑技术知识网站
所在位置:首页 > 科技  > Android

Java游戏编程不完全详解-1

发表时间:2022-03-26来源:网络


前言

1991年,我第一次在DOS操作系统下玩“F-117A Stealth Fighter 2.0 ”游戏,这是一款像素级的模拟器游戏。















于是,我这辈子被种草游戏了--从此爱上了游戏,并且想写一款游戏。于是,我考大学时就报考了计算机系专业,因为别人告诉我大学里会学怎么编程啊、肯定也会编写游戏啊等等...

不过,呵呵,相信大家也知道这是一个谎言!我上了大学后发现别人告诉我的东西根本没有不存在,差一点被害得在毕业时都入不了IT行业,还谈什么游戏开发了。结果搞了10年的EPR应用开发--因为得先填饱自己肚子实现了生存再说哦。

不过,写游戏的梦想一直存在我的心中,直到2011年我转行做了手机游戏的开发,本大黍是第一批使用Coco2d-x开源引擎做手机游戏开发的前辈--第一款是使用0.91版本开发的,直到现在使用的cocos2d-x 4.0版本。

关于C++游戏的话题,我们以后再说。如果大家感兴趣C++游戏开发的话,请耐心等待我们未来出C++系列的文章,请喜欢C++游戏开发的小伙伴们关注+点赞+收藏+评论+转发哦~

好了,言归正传,我们下面解读游戏开发技术,作为计科系出身的程序员我的翻译只能做一个参考,请大家不要当成标准。如果有不足之处,请大家指正和补充。

Java游戏编程之多线程

对于游戏用来说,对游戏第一个的要求就是运行高效--运行一定要流畅,画面一定要美!为保证游戏运行高效和流畅,因此我们必须从游戏的技术选型上就必须要考虑,再加上现代硬件非常高效,操作系统都是多进程和多线程的,因此怎样利用多线程来让程序变得更加高效,这是一个必选题。

当然,我们可以参见“Java多线程第2版不完全详解”一篇文章,以帮助咱们从多角度来理解Java多线程。因为不同的专业书籍对Java多线程的认知是不一样的。呵呵,大家是不是觉得这个说法有一点搞笑,对吧?这个问题在C/C++那里,那就不是一个问题。由于Java虚拟机是不开源的,所以大家都可以各抒己见,百家争鸣。

为什么使用Java编游戏?

Java 1.4版本以后,我们可使用Java平台来开发快速的、全屏幕的和硬件加速(显卡)的游戏!同时,使用Java意味着可以使用复杂的API来简化OOP编程、简化的多线程编程、自动的垃圾回收 ,以及良好的可移植性。除些之外,还有大量开源的库以及优雅、方便的IDE等来使用。

Java相对于C和C++就是它的速度问题,但是如果使用HotSpot VM和独立显卡之后,那么它的游戏运行速度就不是问题了。HotSpot技术是把游戏在运行时编译到本地码中去,加上强大的独立显卡,这时Java编写的游戏就不再会有运行速度的困扰。

什么是多线程?

如果把计算机处理器看成是一个熟练的侍者,而把用户看成是一个任务,那么每个任务都有自己的线程(Thread)。而一个处理器在现代操作中可以并发(concurrently)运行多个线程。比如,我们时常会从互联上下载电影时,还听着音乐的写着代码(^_^)…或者边聊QQ边写代码等。

现代的操作系统并发运行线程时,是把线程的任务分解成更小的块(单元)来处理的—这就叫做并发(concurrency)。一个线程只有一小块时间片来执行,然后该线程被悬空(pre-empted),以便让其它的线程运行,然后如此循环。如是下图:



hread A--线程AThread B--线程BThread A Starts--线程A启动THread B Starts--线程B启动

使用Java创建线程和使用线程

其实Java就是使用线程概念被设计的,所以我们会发现在Java使用线程工作是非常容易的事情,如果想创建并且启动一个新的线程,那么我们只需要创建一个Thread对象的实例,然后呼叫它的start()方法即可。

Thread myThread = new Thread(); myThread.start(); 复制代码

当然该代码没有做任何事情,因为JVM只是创建一个新的系统线程(system thread),然后启动了它,最后呼叫了该线程对象的run()方法,但是run方法没有做任何事情。

使用线程最便捷的方式是直接继承Thread类,然后重写run方法:

public class MyThread extends Thread{ public void run(){ System.out.println(“do something”); } } 复制代码

然后创建这个类的对象,然后启动它:

Thread myThread = new MyThread(); myThread.start(); 复制代码

现在我们两个线程在运行了:主线程和我们现在创建的子线程对象。

继承Thread类非常容易,但是大多数时候我们可能不希望书写一个新的类型就想启动一个线程。比如,我们希望继承另外一个类,但是又想把段代码作为一个线程来运行,那么这种情况下我们需要实现Runnable接口:

public class MyClass extends BaseDAO implements Runnable{ public MyClass(){ Thread thread = new Thread(this); thread.start(); } //实现run方法 public void run(){ System.out.println(“Do something cool here!”); } } 复制代码

以上示例MyClass对象在构造方法启动了一个新的线程。

Thread类把Runnable对象作为它的构造方法的参数,而Runnable对象是在该线程被启动时执行。 有时候我们不想新创建一个类,但是又想封装一个线程,这时时候我们可以使用匿名内部类来实现:

new Thread(){ public void run(){ System.out.println(“Do something cool here”); } }.start(); 复制代码

该示例代码非常简单,但是如果run方法的代码太长,那么它的可读性就非常差(解读:这种写法在现代叫做流式布局写法,非常的流行,但是在Java之初不是赞成的。这个Java之初的时间,我认为应该是Java在没有被卖给Oracle之前,它一直强调严谨的风格,因为我是JDK 1.4版本的程序员,曾经考过Sun公司的Java认证嘛,所以咱们是非常了解什么是最正宗的Java代码风格的。本人并不认为Java 5以后的版本是真正的Java了,它已经变得四不像了,特别是当Java 8引入了函数编程-lambda表达式之后。当然,这个仅代表本人的观点,不喜勿喷哈)。

如果我们需要我们当前的线程等待另外一个线程运行完成,那么使用join()方法:

myThread.join(); 复制代码

该方法非常有用,它一般使用来让一个玩家退出我们的游戏,因为我们需要等待所有线程都运行完成,然后才能做复位的动作。 如果我们需要让线程休息一下,比如让一个线程暂停一会儿,那么使用sleep()方法:

myThread.sleep(1000); 复制代码

这样做结果是让当前运行的线程睡觉一秒钟,但是睡觉不会CPU的时间—当然它不会做梦的。

线程同步

很好,现在我们可以使用多个线程来同时做一些非常cool的事情了,但是这并不表示万事大吉了。因为,如果出现多个线程访问相同的对象或者变量时,那么就会出现同步(Synchronization)的问题。

为什么产生同步?

让我们看一个迷宫游戏,任何线程都可以设置玩家的位置,任何一个线程都可以检查玩家是否还存在。假设,玩家处理位置是x = 0, y = 0.



我来参考翻译一下,请大家不要当成标准,如果不足之处,请指正和补充:

Fork/Join框架定义了一种特别的执行器,它是一种切分和竞争技巧。该框架包含了针对执行器并发任务的优化机制 。Fork/Join是最小的细颗粒度并发单位,它拥有非常小的负载运行效率来运行新的任务,而这些任务是需要放到队列中,并且通过队列化操作来执行的。该框架包含的主要的类和接口如下:

ForkJoinPool--该是一个运行任务的线程池ForkJoinTask--在ForkJoinPool类运行的任务ForkJoinWorkerThread--在ForkJoinPool类中执行任务的线程

ForJoinPool使用示例代码

import java.util.*; import java.util.concurrent.*; /** * 功能:书写一个测试类,用来演示ForkJoinPool类的用法。 * 作者:技术大黍 */ public class ForkJoinPoolTest { public static void main(String[] args) { //创建线程池对象 ForkJoinPool pool = new ForkJoinPool(); IntSum task = new IntSum(3); long sum = pool.invoke(task); System.out.println("Sum is " + sum); } } /** * 功能:书写需要被并发计算整数的类 */ class IntSum extends RecursiveTask { private int count; public IntSum(int count) { this.count = count; } @Override protected Long compute() { long result = 0; if (this.count


补充Executors框架

在《Mastering Concurrency Programming with Java 8》一书中介绍一个Executor的并发框架



我来参考翻译一下,请大家不要当成标准,如果不足之处,请指正和补充:

执行器框架是一种分隔线程的机制,这种机制包含了针对线程的创建和管理,以实现并发任务的效果。我们第三方使用者不用担心线程的创建和管理工作,只需要创建一个任务并且把这些任务对象放到执行器中去就完了。该框架包含的主要类如下:

Exectutor和ExecutorService接口定义了所有执行器的接口行为ThreadPoolExecutor类允许我们第三方使用者从线程池中获取一个执行,并且可以指定最大任务并发数ScheduledThreadPoolExecutor它是一个特殊的执行器,根据我们实际场景要求来调试执行的任务Executors是创建执行器的工具类Callable接口是实现Runnable接口的替代方案--执行一个任务后并且返回一个值(也就是C/C++中的回调函数,这个接口就是Java 5版本引入的新的多线程API,也就是Java从纯面向对象编程思想向函数思想的转变的见证)Future接口是与Callable配套实现回调函数的接口。

Excutors使用示例代码

import java.util.concurrent.*; /** * 功能:书写一个Excutors类实现线程池的示例 * 作者:技术大黍 */ public class ExcutorsTest { public static void main(String[] args) { final int THREAD_COUNT = 3; final int LOOP_COUNT = 3; final int TASK_COUNT = 5; // 从线程池中获取一个执行器对象 ExecutorService exec = Executors.newFixedThreadPool(THREAD_COUNT); // 创建5个任务对象,然后把它们放到执行器中去 for (int i = 1; i


好简单啊~ 但是,为毛面试要问底层实现呢?这是一个值得我们思考的问题。

如果大家有兴趣,那么希望大家能够使用这些补充的知识点来重写前面的线程池代码。并请大家继续参看下一篇“Java游戏编程不完全详解-2”。

最后

记得给大黍❤️关注+点赞+收藏+评论+转发❤️

作者:老九学堂—技术大黍

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


作者:技术大黍
链接:https://juejin.cn/post/6943848673780432926
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜