You can't just fish, use css3 to tell you how to raise fish

I am participating in the individual competition of the Nuggets Community Game Creativity Contest. For details, please see: Game Creativity Contest

introduce

In this issue, we will use css3+vue to make a small fish farming game. We don't use any materials here. They are all drawn by CSS (cough, although it is a paper fish). When we click on the screen, we can put food. When they see food, they will fight for it, and the fish will get bigger when they eat the food. Because it is a small game of leisure and development, there is nothing that cannot be cleared. It depends on who has the largest fish. Bar.

Without further ado, let's first come to Kangkang to show the effect:

VID_20220404_212850.gif

Demo address: codepen.io/jsmask/full…

start

draw fish pond

<div class="main" ref ="pool"></div> 
复制代码
.main{
    width: 100%;
    height: 100vh;
    background: linear-gradient(180deg, #86defc 0%, #71cceb 20%, #73b2f1, #349ef8 83%, #cce293 93%, #e6cd6a 100%);
    overflow: hidden;
    position: relative;
}
复制代码

The background is drawn with a linear-gradient to make a gradient, and a layer can be made. It is relatively clear at first, and then continues to deepen. At the bottom, a part of light yellow is added as the bottom sand.

WeChat screenshot_20220404213300.png

draw small fish

<div class="fish" ref="fish" v-for="(item,index) in fishList" :key="index" 
     :class="{'left':item.direction==-1,[item.type]:true}" 
     :style="{'transform':'translate('+item.x+'px,'+item.y+'px)'}">
    <div class="fish-main" :style="{'transform':'scale('+(1+item.level*0.025)+')'}">
        <div class="fish-body">
            <div class="fish-fins"></div>
        </div>
    </div>
</div>
复制代码
.fish{
   width: 60px;
   height: 30px;
   position:absolute;
   z-index:99;
}

.fish.left .fish-body{
    transform: scaleX(-1);
}
.fish.fish-type1 .fish-body{
    --main-skin:rgb(230,136,72);
}
.fish.fish-type2 .fish-body{
    --main-skin:rgb(230, 90, 72);
}
.fish.fish-type3 .fish-body{
    --main-skin:rgb(72, 127, 230);
}
.fish.fish-type4 .fish-body{
    --main-skin:rgb(241, 207, 94);
}
.fish.fish-type5 .fish-body{
    --main-skin:rgb(82, 151, 100);
}
.fish.fish-type6 .fish-body{
    --main-skin: rgb(255, 117, 117);
}
.fish-main{
    transition: .3s all;
}
.fish-body{
    position: relative;
    margin-left: 6px;
    width: 50px;
    height: 30px;
    border-radius: 50% 50%;
    border-bottom:1px solid rgba(0, 0, 0, .12);
    border-top:1px solid rgba(0, 0, 0, .06);
    background-color: var(--main-skin);
    transition: 1s all;
    transform-origin: 50% 50%;
}
.fish-body::before {
    content: '';
    display: block;
    position: absolute;
    left: -11px;
    width: 0;
    height: 0;
    border-left: solid 25px var(--main-skin);
    border-top: solid 15px transparent;
    border-bottom: solid 15px transparent;
    animation: move2 .24s linear infinite;
}
.fish-body::after {
    content: '';
    display: block;
    position: absolute;
    top: 8px;
    left: 34px;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background-color: black;
    box-shadow: 0px 0px 0 2px white;
}
.fish-fins{
    width: 0;
    height: 0;
    border-left: solid 6px var(--main-skin);
    border-top: solid 3px transparent;
    border-bottom: solid 3px transparent;
    position: absolute;
    top: 17px;
    left: 20px;
    filter: brightness(5.5);
    opacity: .1;
    animation: move .24s linear infinite;
    transform-origin: 100% 100%;
}
@keyframes move{
    0%{
        opacity: .1;
        transform: scaleX(1);
    }

    50%{
        opacity: .15;
        transform: scaleX(1.3);
    }

    100%{
        opacity: .1;
        transform: scaleX(1) ;
    }
}
@keyframes move2{
    0%{
        opacity: .9;
        transform: scaleX(1);
    }

    50%{
        opacity: 1;
        transform: scaleX(1.3);
    }

    100%{
        opacity: .9;
        transform: scaleX(1) ;
    }
}
复制代码

The small fish we draw is composed of four parts: fish body + fish eye + pectoral fin + fish tail. The fish body is a rectangle, the fish eye is a circle and it is very simple to draw, while the pectoral fin and fish tail are triangles, we pass the border To do the drawing, in the pectoral fin part, we increase a part of the brightness through the filter:brightness method to make it more realistic. In addition, the pectoral fins and fish tail can also swing. Here, we use animation to make a zoom-in and zoom-out animation, and then control the base point through transform-origin to achieve the effect of swing. In addition, considering that the fish should have left and right directions, we use scaleX(-1) to flip the fish.

WeChat screenshot_20220404213434.png

Bubbles & Food

<div class="bubble" :key="item.index" v-for="item in bubbleList" :style="{'left':item.x +'px','top':item.y+'px'}">
	<div class="bubble-body"></div>
</div>
<div class="food" v-for="item in foodList" :key="item.index" :style="{'left':item.x +'px','top':item.y+'px'}">
	<div class="food-body"></div>
</div>
复制代码
.bubble{
    width: 5px;
    height: 5px;
    position: absolute;
    animation: up 5s linear;
    animation-fill-mode: forwards;
}
.bubble-body{
    width: 5px;
    height: 5px;
    border:1px solid rgb(255,255,255);
    border-radius: 50%;
    position:absolute;
    left: 60px;
    top: 10px;
    opacity: 1;
    animation: sway 3s linear infinite;
}
.food{
   width: 10px;
   height: 7px;
   position: absolute;
   opacity: 1;
}
.food-body{
   position: absolute;
   width: 10px;
   height: 7px;
   border-radius: 45% 42%;
   background: rgb(82, 57, 43);
   animation: sway 3s linear infinite;
}
@keyframes up{
    0%{
        opacity: 1;
        transform: translateY(0);
    }
    100%{
        opacity: 0;
        transform: translateY(-600px);
    }
}
@keyframes sway{
    0%,20%,40%,60%,80%,100%{
        transform: translateX(0px)  rotate(0);
    }
    10%,30%,50%,70%,90%{
        transform: translateX(-10px)  rotate(30deg);
    }
}
复制代码

The drawing of bubbles and food is very simple, one is a hollow circle and one is a rounded rectangle. The only thing to explain is that I added an animation sway that swings left and right to them, which will make the underwater world more realistic. In addition, there is an up animation. When writing the logic later, it will make the fish protrude bubbles in different cycles and continue to float upward.

WeChat screenshot_20220404213522.png

Fish farming logic

 new Vue({
    el:".main",
    data:{
      fishNum:10,    // 生成鱼的数量
      fishList:[],   // 小鱼数组
      bubbleList:[], // 气泡数组
      foodList:[]    // 食物数组
    },
    mounted() {
      this.init();
    },
    methods: {
      init(){
        // 初始化事件
        this.width = window.innerWidth;
        this.height = window.innerHeight;
        for (let i = 0; i < this.fishNum; i++) {;
           let fish = this.addFish(i);
           this.fishList.push(fish)
        } 
        this.move();
        this.foodMove();
        this.throw();
        window.onresize = () =>{
          this.width = window.innerWidth;
          this.height = window.innerHeight;
         }
      },
      addFish(i){
        // 随机生成鱼的参数               
        return {
          index:`fish_${i}`,
          x: this.random(0,this.width-60),
          y: this.random(15,this.height-30),
          direction:(this.random(0,1)>0.5)?1:-1,
          type: 'fish-type'+~~(this.random(1,6)),
          speed:this.random(1,3),
          bTime:this.random(1,3)*100,
          bMax:this.random(3,10)*100,
          sy:Math.random(0,10),
          level:~~(this.random(0,2))
        }
      },
      move() {
          // 鱼群移动
      },
      addBubble(fish){
       	  // 追加气泡
            const {index,x,y} = fish;
            for (let i = 0; i < this.bubbleList.length; i++) {
              if(this.bubbleList[i].index == index){
                this.bubbleList.splice(i,1);
              }
            }
            this.bubbleList.push({x, y, index });
      },  
      throw(){
          // 投喂
            this.$refs.pool.addEventListener("click",e=>{
              let food = {
                x:e.layerX,
                y:e.layerY
              }
              let index = this.foodList.push(food);                  
            })
      },
      foodMove(){
          // 投喂食物不断下沉
             window.requestAnimationFrame(()=>{
              this.foodList.forEach((food,index)=>{
                food.y++;
                if(food.y>this.height){
                  this.foodList.splice(index,1);
                }
              })
              this.foodMove();
            });
      },
      random(min,max){
        return min + Math.random()*max
      }
})
复制代码

At first, we generated the parameters of different fish, such as position, direction, speed, size, etc., and then added them to the fishList array for control. Note here that the coordinates of food, small fish, and bubbles are changed when vue's style has been bound to translate.

addBubble method: We will use it later when we write the logic of continuously drawing fish swimming (that is, the move method). Here, the coordinates and the fish object that spit out bubbles are first added to the bubbleList array, because the up animation was just written with css, and the bubble animation is a section. It will disappear after time. When the fish spit out the bubble next time, it will be removed from the bubbleList array and added again, and it can be played again.

Throw method: register a click event, and send the coordinates of a food to the foodList array for each click.

foodMove method: It continuously draws the animation of the food falling, and judges that if it sinks to the bottom, let it disappear.

function move() {
      window.requestAnimationFrame(() => {    
        this.fishList.forEach( fish => {
          
          // 到达临界值时吐气泡
          if (++fish.bTime > fish.bMax) {
            fish.bTime = 0;
            this.addBubble(fish);
          }
            
          // 找到最近的食物
          if (this.foodList.length > 0) {
            let foodIndex = 0;
            if (this.foodList.length > 1) {
              for (let i = 0, sub = null; i < this.foodList.length; i++) {
                let num = Math.abs(this.foodList[i].x - fish.x);
                if (sub == null) sub = num;
                if (num < sub) {
                  sub = num;
                  foodIndex = i;
                }
              }
            }
            
            // 根据最近的食物找到改变与的方向
            let food = this.foodList[foodIndex];
            let dx = food.x - fish.x;
            let dy = food.y - fish.y;
            if (dx >= 0) {
              fish.direction = 1;
            } else {
              fish.direction = -1;
            }

            // 计算方向改变鱼的移动,如果触碰到食物则升级
            let angle = Math.atan2(dy, dx);
            if (dx < 10 && dx > -10 && dy < 10 && dy > -10) {
              fish.level++; // 升级长胖
              this.foodList.splice(foodIndex, 1);
              fish.direction = Math.random() > 0.5 ? 1 : -1;
            } else {
              let vx = fish.speed * 1.2 * Math.cos(angle);
              let vy = fish.speed * 1.2 * Math.sin(angle);
              fish.x += vx;
              fish.y += vy;
            }
          } else {
            fish.x += fish.speed * fish.direction;
            fish.sy += 0.01;
            fish.y += Math.cos(fish.sy) * 2;
          }
            
		  // 边界判断
          if (fish.x < -60) {
            fish.x = -60;
            fish.direction *= -1;
            fish.speed = this.random(1, 3);
          }
          if (fish.x > this.width + 30) {
            fish.x = this.width + 30;
            fish.direction *= -1;
            fish.speed = this.random(1, 3);
          }
          if (fish.y < 0) {
            fish.y = 0;
          }
          if (fish.y > this.height - 30) {
            fish.y = this.height - 30;
          }
        });

        this.move();
   });
}
复制代码

There is still a lot of fish's swimming logic. What each paragraph mainly does is written in the comments. The real swimming logic of fish is to calculate the difference between the target point and the current fish coordinates (ie dx and dy), and then let angle = Math.atan2(dy, dx)calculate Angle out, so as to obtain acceleration in different directions (ie vx and vy), thereby changing the current coordinates of the fish.

WeChat screenshot_20220404213659.png

Epilogue

The css fish farming game has been completed here, how do you feel? Perhaps, you found that if there are too many fish, the redrawing nodes will take up a lot of memory. Of course, you can try to use canvas to draw, which is also a good choice. The core logic of js is roughly the same.

PS: This work is a small work for personal entertainment in the early days. You can view the source code in codepen. If newcomers try to use this practice, it feels that it will have some effect on your css or js improvement. Fish farming itself is also a matter of self-cultivation. Only by practicing without rushing can you lay a solid foundation. Let’s work hard together~

Guess you like

Origin juejin.im/post/7085138867560382494