写这个小游戏的初衷
同学一起互助学习
前两天和一个计科的高中同学交流想法,有讲到合理规划时间,然后用分出来的时间去网上找资源学一些项目,这不,就有了这个小游戏了
和同学交流的时候,对方说每周花一个小时来学习,然后写项目,我本来觉得一个小时太少了,一周那么多时间,这一个小时绰绰有余啊
然后真相了,大二生一枚,虽然课看起来不多吧,但是杂七杂八的事情也挺多的,比如本来今天想学一下语言,然后被叫去干活,一会开班会,一会有有党课🌝
但是第一周开始决定要学写项目了,三分钟热度还是要有的,周末肝了一个晚上和一个上午用来改bug,效果还是看得见的
项目资源
先上小破站搜Java项目,看到60多个小时的果断放弃了
滑鼠一滑,两小时,黄金矿工,好,就它了
废话不多说,直接上效果吧
视频放不过来,只能截一张图了
上码
背景类
package com.sxt;
import javax.swing.*;
import java.awt.*;
/**
* 背景
*/
public class Bg extends JFrame {
//关卡数
static int level=1;
//目标得分
int goal=level*15;
//总分
static int count=0;
//药水数量
static int waterNum=3;
//药水状态,默认false,true为正在使用
static boolean waterFlag=false;
//开始时间
long startTime;
//结束时间
long endTime;
//药水价格
int price=(int)(Math.random()*10);
//是否进入商店,初始不购买
boolean shop=false;
//载入图片
Image bg=Toolkit.getDefaultToolkit().getImage("imgs/bg.jpg");
Image bg1=Toolkit.getDefaultToolkit().getImage("imgs/bg1.jpg");
Image peo=Toolkit.getDefaultToolkit().getImage("imgs/peo.png");
Image water=Toolkit.getDefaultToolkit().getImage("imgs/water.png");
//绘制
void paintSelf(Graphics g){
g.drawImage(bg1,0,0,null);
g.drawImage(bg,0,200,null);
switch(GameWin.state){
case 0->{
drawWord(g,80,Color.green,"准备开始",200,400);
}
case 1->{
g.drawImage(peo,310,50,null);
drawWord(g,30,Color.black,"积分:"+count,30,150);
//药水组件
g.drawImage(water,450,40,null);
drawWord(g,30,Color.black,"*"+waterNum,510,70);
//关卡数
drawWord(g,20,Color.black,"第"+level+"关",30,60);
//目标积分
drawWord(g,30,Color.black,"目标:"+goal,30,110);
//时间组件
endTime=System.currentTimeMillis();
long tim=20-(endTime-startTime)/1000;
drawWord(g,30,Color.black,"时间"+(tim>0?tim:0),520,150);
}
case 2->{//商店界面
g.drawImage(water,300,400,null);
drawWord(g,30,Color.black,"药水价格:"+price,300,500);
drawWord(g,30,Color.black,"是否购买?",300,550);
if(shop){
count=count-price;
waterNum++;
shop=false;
GameWin.state=1;
startTime=System.currentTimeMillis();
}
}
case 3->{
drawWord(g,80,Color.cyan,"失败",250,350);
drawWord(g,80,Color.cyan,"积分:"+count,200,450);
}
case 4->{
drawWord(g,80,Color.red,"成功",250,350);
drawWord(g,80,Color.red,"积分:"+count,200,450);
}
}
}
//true为倒计时完成,false为正在倒计时
boolean gameTime(){
long tim=(endTime-startTime)/1000;
if(tim>20)return true;
return false;
}
//重置元素
void reGame(){
//关卡数
level=1;
//目标得分
goal=level*15;
//总分
count=0;
//药水数量
waterNum=3;
//药水状态,默认false,true为正在使用
waterFlag=false;
}
//绘制字符串
public static void drawWord(Graphics g,int size,Color color,String str,int x,int y){
g.setColor(color);
g.setFont(new Font("仿宋" ,Font.BOLD,size));
g.drawString(str,x,y);
}
}
Object类
用来作石块和金块的父类
package com.sxt;
import java.awt.*;
public class Object {
//坐标
int x;
int y;
//宽高
int width;
int height;
//图片
Image img;
//标记是否可移动
boolean flag=false;//true可移动,false 不可移动
//质量
int m;
//积分
int count;
//类型,金块==1,石块==2
int type;
void paintSelf(Graphics g){
g.drawImage(img,x,y,null);
}
public int getWidth() {
return width;
}
//获取矩形,用于判断是否堆叠
public Rectangle getRec(){
return new Rectangle(x,y,width,height);
}
}
Gold类
package com.sxt;
import java.awt.*;
public class Gold extends Object{
Gold(){
this.x=(int)(Math.random()*700);
this.y=(int)(Math.random()*550+300);
this.width=52;
this.height=52;
this.flag=false;
this.m=30;
this.count=4;
this.type=1;
this.img= Toolkit.getDefaultToolkit().getImage("imgs/gold1.gif");
}
}
class GoldMin extends Gold{
GoldMin(){
this.width=36;
this.height=36;
this.m=15;
this.count=2;
this.img= Toolkit.getDefaultToolkit().getImage("imgs/gold0.gif");
}
}
class GoldPlus extends Gold{
GoldPlus(){
this.x=(int)(Math.random()*650);
this.width=105;
this.height=105;
this.m=60;
this.count=8;
this.img= Toolkit.getDefaultToolkit().getImage("imgs/gold2.gif");
}
}
Rock类
package com.sxt;
import java.awt.*;
/**
* 石块类
*/
public class Rock extends Object{
Rock(){
this.x=(int)(Math.random()*700);
this.y=(int)(Math.random()*550+300);
this.width=71;
this.height=71;
this.flag=false;
this.m=50;
this.count=1;
this.type=2;
this.img= Toolkit.getDefaultToolkit().getImage("imgs/rock1.png");
}
}
Line类
游戏时的钩子上的线收缩拉取
package com.sxt;
import java.awt.*;
import static java.awt.Color.red;
public class Line {
//(x,y)起点坐标
int x=380;
int y=180;
//终点坐标
int endx=500;
int endy=500;
//线长
double length=100;
//线长最小值
double MIN_length=100;
//线长最大值
double MAX_length=750;
double n=0;
//方向
int dir=1;
//状态 0-->摇摆 1-->抓取 2-->收回 3-->抓取返回
int state;
//钩爪图片
Image hook=Toolkit.getDefaultToolkit().getImage("imgs/hook.png");
GameWin frame;
Line(GameWin frame){
this.frame=frame;
}
//碰撞检测,检测金块和石块是否被抓取
void logic(){
for(Object obj:this.frame.objectList){
if(endx>obj.x&&endx<obj.x+obj.width
&&endy>obj.y&&endy<obj.y+obj.height){
state=3;
obj.flag=true;//接触到了则可以移动
}
}
}
//绘制方法
void lines(Graphics g){//画线
endx=(int)(x+length*Math.cos(n*Math.PI));
endy=(int)(y+length*Math.sin(n*Math.PI));
g.setColor(red);
g.drawLine(x-1,y,endx-1,endy);
g.drawLine(x,y,endx,endy);
g.drawLine(x+1,y,endx+1,endy);
g.drawImage(hook,endx-36,endy-2,null);
}
void paintSelf(Graphics g){
logic();
switch(state){
case 0->{if(n<0.1){dir=1;}
else if(n>0.9){dir=-1;}
n=n+0.005*dir;
lines(g);}
case 1->{//线延长
if(length<MAX_length){
length+=5;
lines(g);
}else{
state=2;//到收回状态
}
}
case 2->{//线收回
if(length>MIN_length) {
length -= 5;
lines(g);
}else{
state=0;//回到摇摆的状态
}
}
case 3->{
int m=1;
if(length>MIN_length) {
length -= 5;
lines(g);
for(Object obj:this.frame.objectList){
if(obj.flag){
m=obj.m;
obj.x=endx-obj.getWidth()/2;
obj.y=endy;
if(length<=100){
obj.x=150;//金块移到屏幕外面
obj.y=-150;
obj.flag=false;
Bg.waterFlag=false;
Bg.count+=obj.count;//加分
state=0;//回到摇摆的状态
}
if(Bg.waterFlag){//使用了药水
if(obj.type==1){//金块提高刷新频率,实现快速抓取
m=1;
}else if(obj.type==2){
obj.x=150;//金块移到屏幕外面
obj.y=-150;
obj.flag=false;
Bg.waterFlag=false;
state=2;
}
}
}
}
}
try {
Thread.sleep(m);//根据重量来设置延时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//重置线
void reGame(){
n=0;
length=100;
}
}
GameWin类,主角登场
package com.sxt;
//窗口绘制
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
public class GameWin extends JFrame {
//0:未开始 1:游戏中 2:商店 3:失败 4:胜利
static int state;
//存储金块,石块
List<Object> objectList = new ArrayList<>();
Bg bg = new Bg();
Line line = new Line(this);
{
//是否可以放置
boolean isPlace=true;
//金块,按照一定的比例添加不同的大小
for (int i = 0; i < 11; i++) {
double random=Math.random();
Gold gold;//存放当前生成的金块
if(random<0.3){
gold=new GoldMin();
}else if(random<0.7){
gold=new GoldPlus();
}else {
gold=new Gold();
}
for(Object obj:objectList){
if(gold.getRec().intersects(obj.getRec())){
//金块重叠,不可放置,需要重新生成
isPlace=false;
}
}
if(isPlace){
objectList.add(gold);
}else{
isPlace=true;
i--;
}
}
for (int i = 0; i < 5; i++) {
Rock rock=new Rock();
for(Object obj:objectList){
if(rock.getRec().intersects(obj.getRec())){
isPlace=false;
}
}
if(isPlace){
objectList.add(rock);
}else{
isPlace=true;
i--;
}
}
}
Image offScreenImage;
void launch(){
this.setVisible(true);
this.setSize(768,1000);
this.setLocationRelativeTo(null);
this.setTitle("黄金矿工");
setDefaultCloseOperation(EXIT_ON_CLOSE);
addMouseListener(new MouseAdapter(){
@Override
public void mouseClicked(MouseEvent e){
super.mouseClicked(e);
switch(state){
case 0->{
if(e.getButton()==3){
state=1;
bg.startTime=System.currentTimeMillis();//记录开始时间
}
}
case 1->{
if(e.getButton()==1&&line.state==0){//鼠标左键=1,右键=3,滚轮=2
line.state=1;
}
if(e.getButton()==3&&line.state==3&&Bg.waterNum>0){//右键
Bg.waterFlag=true;
Bg.waterNum--;
}
}
case 2->{//商店界面
if(e.getButton()==1){
bg.shop=true;
}
if(e.getButton()==3){
state=1;
bg.startTime=System.currentTimeMillis();
}
}
case 3,4->{
if(e.getButton()==1){
state=0;
bg.reGame();
line.reGame();
}
}
}
}
});
while(true){
repaint();//重新绘制
nextLevel();
try {
Thread.sleep(10);//延时,10ms刷新一次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//下一关
public void nextLevel(){
if(bg.gameTime()&&state==1){
if(Bg.count>=bg.goal){
if(Bg.level==5){
state=4;//进入成功状态
}else{
state=2;
Bg.level++;
// bg.startTime=System.currentTimeMillis();
}
}else{
state=3;
}
dispose();
GameWin gameWin1= new GameWin();
gameWin1.launch();
}
}
@Override
public void paint(Graphics g){
offScreenImage=this.createImage(768,1000);//用于双缓存刷频
Graphics gImage=offScreenImage.getGraphics();
bg.paintSelf(gImage);
if(state==1){
//绘制物体
for(Object obj:objectList){
obj.paintSelf(gImage);
}
line.paintSelf(gImage);
}
g.drawImage(offScreenImage,0,0,null);
}
public static void main(String[] args) {
GameWin gameWin=new GameWin();
gameWin.launch();
}
}
从这个小游戏中get到的小tips
- 使用双缓存技术刷新页面
-
需求:
在项目初期,背景中的图片时层叠加入的,刷新一次就每个图片直接再通过画笔drawImage()一次到画布(姑且这么叫)上
这样就造成了游戏界面频繁闪现,也就是一卡一卡的 -
解决办法
重写paint函数,创建一个新的Image,每一次刷新都将图片按次序添加到新的Image上,添加完毕后再将整个Image添加到实际的游戏界面上,这样每次看见的游戏界面的图片都是完整的,不会存在这个刚刚刷新出来,另一部分图片还是之前的,看起来游戏很稳定
把paint函数挑出来
@Override
public void paint(Graphics g){
offScreenImage=this.createImage(768,1000);//用于双缓存刷频
Graphics gImage=offScreenImage.getGraphics();//一个新的画笔
bg.paintSelf(gImage);
if(state==1){//1为游戏中
//绘制物体
for(Object obj:objectList){
obj.paintSelf(gImage);//使用后新画笔画图像
}
line.paintSelf(gImage);//新画笔画线
}
g.drawImage(offScreenImage,0,0,null);//将画好的整个Image画到游戏界面
}
小游戏不完善之处
游戏界面的大小比我实际设置的大小要大一些,(以高度为例,我设置的高度为1000,但是实际的heigth超过了1000,宽度也是这样)以至于游戏界面在我的电脑上不能完全显示,还有一截在屏幕下面看不见,这个问题在视频中是没有的,上网查了一下也不知道是什么问题,这个问题先抛在这
咳咳 手动分割线
显示图片大小 …更新…
关于这个显示图片的问题
我总结下来是我的电脑和老师电脑不一样,更确切地说,是电脑显示的分辨率和缩放度可能是不一样的
我的电脑是1902*1080的分辨率,缩放150%
然后老师演示的电脑好像不一样,
这点可能有点不同
以下尝试修改缩放度来达到效果
不过相当不建议,能改代码就改代码,能不动电脑就别动电脑
设置1:
1902*1034p
150%
效果1:
设置2:
效果2:
设置3:
效果3:
我觉得缩放度在110%到125%之间应该是效果比较好
但是怕改了之后麻烦太多,就收回了手
当然,也可以通过计算图片在自己当前电脑上显示的占比,然后经过一通计算更改图片的像素,当然也可以写个方法让电脑自己算
我总觉得还有什么更好的办法
以上改缩放度真心不建议
但是怎么改代码。。。我。。。不知道了。。。
球球路过的大佬捞一捞(●’◡’●)
这个小游戏还是很简单,很基础的,在这个基础上还可以添加一些操作或者选项,让这个小游戏更好玩
譬如通关时背景更炫酷一些,
药水可以在任何时候购买,通过游戏界面按钮来实现交互(是否开始,是否购买药水这些)
有暂停功能
···
写完小游戏
还是超级开心的,一个小游戏在自己的手中一步一步完善,最后的运行效果也还不错,(虽说是跟着别人一部一部写的)也提高了一点点的代码能力和代码量,还是不错的
而且写代码真的有时候会停不下来,视频两小时,然后给自己的时间本来只有每周花一小时来写,但是写一半就放着让人不舒服,于是乎熬夜也要写完才罢休
今天(不,是昨天)正好是1024,纪念2021程序员节